From 6e58ac6aab43922789dd9a9e00d0bfd869d10a08 Mon Sep 17 00:00:00 2001 From: Adrien Castex Date: Mon, 26 Jun 2017 19:15:06 +0200 Subject: [PATCH] Implemented the v2 --- lib/helper/v2/IfParser.d.ts | 5 + lib/helper/v2/IfParser.js | 134 ++ lib/helper/v2/export.d.ts | 3 + lib/helper/v2/export.js | 8 + lib/manager/export.v2.d.ts | 1 + lib/manager/export.v2.js | 6 + lib/manager/v2/Path.d.ts | 15 + lib/manager/v2/Path.js | 63 + lib/manager/v2/export.d.ts | 5 + lib/manager/v2/export.js | 10 + lib/manager/v2/fileSystem/CommonTypes.d.ts | 18 + lib/manager/v2/fileSystem/CommonTypes.js | 14 + lib/manager/v2/fileSystem/ContextInfo.d.ts | 55 + lib/manager/v2/fileSystem/ContextInfo.js | 2 + .../v2/fileSystem/ContextualFileSystem.d.ts | 67 + .../v2/fileSystem/ContextualFileSystem.js | 83 + lib/manager/v2/fileSystem/FileSystem.d.ts | 99 ++ lib/manager/v2/fileSystem/FileSystem.js | 515 ++++++ lib/manager/v2/fileSystem/LockManager.d.ts | 17 + lib/manager/v2/fileSystem/LockManager.js | 42 + .../v2/fileSystem/PropertyManager.d.ts | 19 + lib/manager/v2/fileSystem/PropertyManager.js | 26 + lib/manager/v2/fileSystem/Resource.d.ts | 63 + lib/manager/v2/fileSystem/Resource.js | 74 + lib/manager/v2/fileSystem/Serialization.d.ts | 22 + lib/manager/v2/fileSystem/Serialization.js | 48 + .../v2/fileSystem/StandardMethods.d.ts | 16 + lib/manager/v2/fileSystem/StandardMethods.js | 190 +++ lib/manager/v2/fileSystem/export.d.ts | 9 + lib/manager/v2/fileSystem/export.js | 13 + lib/manager/v2/instances/FTPFileSystem.d.ts | 41 + lib/manager/v2/instances/FTPFileSystem.js | 269 ++++ .../v2/instances/PhysicalFileSystem.d.ts | 37 + .../v2/instances/PhysicalFileSystem.js | 207 +++ .../v2/instances/VirtualFileSystem.d.ts | 51 + lib/manager/v2/instances/VirtualFileSystem.js | 200 +++ lib/resource/export.v2.d.ts | 5 + lib/resource/export.v2.js | 11 + lib/server/v2/RequestContext.d.ts | 67 + lib/server/v2/RequestContext.js | 283 ++++ lib/server/v2/WebDAVRequest.d.ts | 11 + lib/server/v2/WebDAVRequest.js | 6 + lib/server/v2/WebDAVServerOptions.d.ts | 35 + lib/server/v2/WebDAVServerOptions.js | 36 + lib/server/v2/commands/Commands.d.ts | 30 + lib/server/v2/commands/Commands.js | 32 + lib/server/v2/commands/Copy.d.ts | 7 + lib/server/v2/commands/Copy.js | 15 + lib/server/v2/commands/Delete.d.ts | 7 + lib/server/v2/commands/Delete.js | 36 + lib/server/v2/commands/Get.d.ts | 7 + lib/server/v2/commands/Get.js | 119 ++ lib/server/v2/commands/Head.d.ts | 7 + lib/server/v2/commands/Head.js | 51 + lib/server/v2/commands/Mkcol.d.ts | 7 + lib/server/v2/commands/Mkcol.js | 49 + lib/server/v2/commands/Move.d.ts | 8 + lib/server/v2/commands/Move.js | 74 + lib/server/v2/commands/NotImplemented.d.ts | 5 + lib/server/v2/commands/NotImplemented.js | 13 + lib/server/v2/commands/Options.d.ts | 5 + lib/server/v2/commands/Options.js | 15 + lib/server/v2/commands/Post.d.ts | 2 + lib/server/v2/commands/Post.js | 4 + lib/server/v2/commands/Propfind.d.ts | 7 + lib/server/v2/commands/Propfind.js | 342 ++++ lib/server/v2/commands/Proppatch.d.ts | 7 + lib/server/v2/commands/Proppatch.js | 80 + lib/server/v2/commands/Put.d.ts | 8 + lib/server/v2/commands/Put.js | 58 + lib/server/v2/commands/Unlock.d.ts | 7 + lib/server/v2/commands/Unlock.js | 74 + lib/server/v2/export.d.ts | 5 + lib/server/v2/export.js | 10 + lib/server/v2/webDAVServer/BeforeAfter.d.ts | 6 + lib/server/v2/webDAVServer/BeforeAfter.js | 30 + lib/server/v2/webDAVServer/Events.d.ts | 15 + lib/server/v2/webDAVServer/Events.js | 54 + lib/server/v2/webDAVServer/Persistence.d.ts | 7 + lib/server/v2/webDAVServer/Persistence.js | 126 ++ lib/server/v2/webDAVServer/StartStop.d.ts | 3 + lib/server/v2/webDAVServer/StartStop.js | 93 ++ lib/server/v2/webDAVServer/Types.d.ts | 0 lib/server/v2/webDAVServer/Types.js | 12 + lib/server/v2/webDAVServer/WebDAVServer.d.ts | 72 + lib/server/v2/webDAVServer/WebDAVServer.js | 195 +++ lib/user/v2/IUser.d.ts | 7 + lib/user/v2/IUser.js | 2 + lib/user/v2/IUserManager.d.ts | 4 + lib/user/v2/IUserManager.js | 2 + .../v2/authentication/HTTPAuthentication.d.ts | 8 + .../v2/authentication/HTTPAuthentication.js | 2 + .../HTTPBasicAuthentication.d.ts | 13 + .../authentication/HTTPBasicAuthentication.js | 43 + .../HTTPDigestAuthentication.d.ts | 15 + .../HTTPDigestAuthentication.js | 71 + lib/user/v2/export.d.ts | 9 + lib/user/v2/export.js | 9 + .../v2/privilege/FakePrivilegeManager.d.ts | 18 + lib/user/v2/privilege/FakePrivilegeManager.js | 36 + lib/user/v2/privilege/IPrivilegeManager.d.ts | 35 + lib/user/v2/privilege/IPrivilegeManager.js | 38 + .../privilege/SimplePathPrivilegeManager.d.ts | 24 + .../privilege/SimplePathPrivilegeManager.js | 81 + .../v2/privilege/SimplePrivilegeManager.d.ts | 31 + .../v2/privilege/SimplePrivilegeManager.js | 29 + lib/user/v2/simple/SimpleUser.d.ts | 9 + lib/user/v2/simple/SimpleUser.js | 13 + lib/user/v2/simple/SimpleUserManager.d.ts | 12 + lib/user/v2/simple/SimpleUserManager.js | 44 + lib/user/v2/userManager/IListUserManager.d.ts | 6 + lib/user/v2/userManager/IListUserManager.js | 2 + .../v2/userManager/ITestableUserManager.d.ts | 5 + .../v2/userManager/ITestableUserManager.js | 2 + src/helper/v2/IfParser.ts | 183 +++ src/helper/v2/export.ts | 4 + src/manager/v2/Path.ts | 86 + src/manager/v2/export.ts | 6 + src/manager/v2/fileSystem/CommonTypes.ts | 38 + src/manager/v2/fileSystem/ContextInfo.ts | 66 + .../v2/fileSystem/ContextualFileSystem.ts | 166 ++ src/manager/v2/fileSystem/FileSystem.ts | 707 +++++++++ src/manager/v2/fileSystem/FileSystem.ts.old | 1395 +++++++++++++++++ src/manager/v2/fileSystem/LockManager.ts | 73 + src/manager/v2/fileSystem/PropertyManager.ts | 54 + src/manager/v2/fileSystem/Resource.ts | 151 ++ src/manager/v2/fileSystem/Serialization.ts | 85 + src/manager/v2/fileSystem/StandardMethods.ts | 263 ++++ src/manager/v2/fileSystem/export.ts | 10 + src/manager/v2/instances/FTPFileSystem.ts | 357 +++++ .../v2/instances/PhysicalFileSystem.ts | 284 ++++ src/manager/v2/instances/VirtualFileSystem.ts | 279 ++++ src/resource/export.v2.ts | 7 + src/server/v2/RequestContext.ts | 410 +++++ src/server/v2/WebDAVRequest.ts | 13 + src/server/v2/WebDAVServerOptions.ts | 60 + src/server/v2/commands/Commands.ts | 31 + src/server/v2/commands/Copy.ts | 20 + src/server/v2/commands/Delete.ts | 39 + src/server/v2/commands/Get.ts | 130 ++ src/server/v2/commands/Head.ts | 58 + src/server/v2/commands/Mkcol.ts | 54 + src/server/v2/commands/Move.ts | 86 + src/server/v2/commands/NotImplemented.ts | 10 + src/server/v2/commands/Options.ts | 12 + src/server/v2/commands/Post.ts | 3 + src/server/v2/commands/Propfind.ts | 534 +++++++ src/server/v2/commands/Proppatch.ts | 96 ++ src/server/v2/commands/Put.ts | 67 + src/server/v2/commands/Unlock.ts | 92 ++ src/server/v2/export.ts | 6 + src/server/v2/webDAVServer/BeforeAfter.ts | 43 + src/server/v2/webDAVServer/Events.ts | 80 + src/server/v2/webDAVServer/Persistence.ts | 154 ++ src/server/v2/webDAVServer/StartStop.ts | 122 ++ src/server/v2/webDAVServer/Types.ts | 25 + src/server/v2/webDAVServer/WebDAVServer.ts | 336 ++++ src/user/v2/IUser.ts | 10 + src/user/v2/IUserManager.ts | 6 + .../v2/authentication/HTTPAuthentication.ts | 11 + .../authentication/HTTPBasicAuthentication.ts | 51 + .../HTTPDigestAuthentication.ts | 90 ++ src/user/v2/export.ts | 16 + src/user/v2/privilege/FakePrivilegeManager.ts | 24 + src/user/v2/privilege/IPrivilegeManager.ts | 104 ++ .../privilege/SimplePathPrivilegeManager.ts | 90 ++ .../v2/privilege/SimplePrivilegeManager.ts | 57 + src/user/v2/simple/SimpleUser.ts | 16 + src/user/v2/simple/SimpleUserManager.ts | 59 + src/user/v2/userManager/IListUserManager.ts | 8 + .../v2/userManager/ITestableUserManager.ts | 7 + 171 files changed, 12419 insertions(+) create mode 100644 lib/helper/v2/IfParser.d.ts create mode 100644 lib/helper/v2/IfParser.js create mode 100644 lib/helper/v2/export.d.ts create mode 100644 lib/helper/v2/export.js create mode 100644 lib/manager/export.v2.d.ts create mode 100644 lib/manager/export.v2.js create mode 100644 lib/manager/v2/Path.d.ts create mode 100644 lib/manager/v2/Path.js create mode 100644 lib/manager/v2/export.d.ts create mode 100644 lib/manager/v2/export.js create mode 100644 lib/manager/v2/fileSystem/CommonTypes.d.ts create mode 100644 lib/manager/v2/fileSystem/CommonTypes.js create mode 100644 lib/manager/v2/fileSystem/ContextInfo.d.ts create mode 100644 lib/manager/v2/fileSystem/ContextInfo.js create mode 100644 lib/manager/v2/fileSystem/ContextualFileSystem.d.ts create mode 100644 lib/manager/v2/fileSystem/ContextualFileSystem.js create mode 100644 lib/manager/v2/fileSystem/FileSystem.d.ts create mode 100644 lib/manager/v2/fileSystem/FileSystem.js create mode 100644 lib/manager/v2/fileSystem/LockManager.d.ts create mode 100644 lib/manager/v2/fileSystem/LockManager.js create mode 100644 lib/manager/v2/fileSystem/PropertyManager.d.ts create mode 100644 lib/manager/v2/fileSystem/PropertyManager.js create mode 100644 lib/manager/v2/fileSystem/Resource.d.ts create mode 100644 lib/manager/v2/fileSystem/Resource.js create mode 100644 lib/manager/v2/fileSystem/Serialization.d.ts create mode 100644 lib/manager/v2/fileSystem/Serialization.js create mode 100644 lib/manager/v2/fileSystem/StandardMethods.d.ts create mode 100644 lib/manager/v2/fileSystem/StandardMethods.js create mode 100644 lib/manager/v2/fileSystem/export.d.ts create mode 100644 lib/manager/v2/fileSystem/export.js create mode 100644 lib/manager/v2/instances/FTPFileSystem.d.ts create mode 100644 lib/manager/v2/instances/FTPFileSystem.js create mode 100644 lib/manager/v2/instances/PhysicalFileSystem.d.ts create mode 100644 lib/manager/v2/instances/PhysicalFileSystem.js create mode 100644 lib/manager/v2/instances/VirtualFileSystem.d.ts create mode 100644 lib/manager/v2/instances/VirtualFileSystem.js create mode 100644 lib/resource/export.v2.d.ts create mode 100644 lib/resource/export.v2.js create mode 100644 lib/server/v2/RequestContext.d.ts create mode 100644 lib/server/v2/RequestContext.js create mode 100644 lib/server/v2/WebDAVRequest.d.ts create mode 100644 lib/server/v2/WebDAVRequest.js create mode 100644 lib/server/v2/WebDAVServerOptions.d.ts create mode 100644 lib/server/v2/WebDAVServerOptions.js create mode 100644 lib/server/v2/commands/Commands.d.ts create mode 100644 lib/server/v2/commands/Commands.js create mode 100644 lib/server/v2/commands/Copy.d.ts create mode 100644 lib/server/v2/commands/Copy.js create mode 100644 lib/server/v2/commands/Delete.d.ts create mode 100644 lib/server/v2/commands/Delete.js create mode 100644 lib/server/v2/commands/Get.d.ts create mode 100644 lib/server/v2/commands/Get.js create mode 100644 lib/server/v2/commands/Head.d.ts create mode 100644 lib/server/v2/commands/Head.js create mode 100644 lib/server/v2/commands/Mkcol.d.ts create mode 100644 lib/server/v2/commands/Mkcol.js create mode 100644 lib/server/v2/commands/Move.d.ts create mode 100644 lib/server/v2/commands/Move.js create mode 100644 lib/server/v2/commands/NotImplemented.d.ts create mode 100644 lib/server/v2/commands/NotImplemented.js create mode 100644 lib/server/v2/commands/Options.d.ts create mode 100644 lib/server/v2/commands/Options.js create mode 100644 lib/server/v2/commands/Post.d.ts create mode 100644 lib/server/v2/commands/Post.js create mode 100644 lib/server/v2/commands/Propfind.d.ts create mode 100644 lib/server/v2/commands/Propfind.js create mode 100644 lib/server/v2/commands/Proppatch.d.ts create mode 100644 lib/server/v2/commands/Proppatch.js create mode 100644 lib/server/v2/commands/Put.d.ts create mode 100644 lib/server/v2/commands/Put.js create mode 100644 lib/server/v2/commands/Unlock.d.ts create mode 100644 lib/server/v2/commands/Unlock.js create mode 100644 lib/server/v2/export.d.ts create mode 100644 lib/server/v2/export.js create mode 100644 lib/server/v2/webDAVServer/BeforeAfter.d.ts create mode 100644 lib/server/v2/webDAVServer/BeforeAfter.js create mode 100644 lib/server/v2/webDAVServer/Events.d.ts create mode 100644 lib/server/v2/webDAVServer/Events.js create mode 100644 lib/server/v2/webDAVServer/Persistence.d.ts create mode 100644 lib/server/v2/webDAVServer/Persistence.js create mode 100644 lib/server/v2/webDAVServer/StartStop.d.ts create mode 100644 lib/server/v2/webDAVServer/StartStop.js create mode 100644 lib/server/v2/webDAVServer/Types.d.ts create mode 100644 lib/server/v2/webDAVServer/Types.js create mode 100644 lib/server/v2/webDAVServer/WebDAVServer.d.ts create mode 100644 lib/server/v2/webDAVServer/WebDAVServer.js create mode 100644 lib/user/v2/IUser.d.ts create mode 100644 lib/user/v2/IUser.js create mode 100644 lib/user/v2/IUserManager.d.ts create mode 100644 lib/user/v2/IUserManager.js create mode 100644 lib/user/v2/authentication/HTTPAuthentication.d.ts create mode 100644 lib/user/v2/authentication/HTTPAuthentication.js create mode 100644 lib/user/v2/authentication/HTTPBasicAuthentication.d.ts create mode 100644 lib/user/v2/authentication/HTTPBasicAuthentication.js create mode 100644 lib/user/v2/authentication/HTTPDigestAuthentication.d.ts create mode 100644 lib/user/v2/authentication/HTTPDigestAuthentication.js create mode 100644 lib/user/v2/export.d.ts create mode 100644 lib/user/v2/export.js create mode 100644 lib/user/v2/privilege/FakePrivilegeManager.d.ts create mode 100644 lib/user/v2/privilege/FakePrivilegeManager.js create mode 100644 lib/user/v2/privilege/IPrivilegeManager.d.ts create mode 100644 lib/user/v2/privilege/IPrivilegeManager.js create mode 100644 lib/user/v2/privilege/SimplePathPrivilegeManager.d.ts create mode 100644 lib/user/v2/privilege/SimplePathPrivilegeManager.js create mode 100644 lib/user/v2/privilege/SimplePrivilegeManager.d.ts create mode 100644 lib/user/v2/privilege/SimplePrivilegeManager.js create mode 100644 lib/user/v2/simple/SimpleUser.d.ts create mode 100644 lib/user/v2/simple/SimpleUser.js create mode 100644 lib/user/v2/simple/SimpleUserManager.d.ts create mode 100644 lib/user/v2/simple/SimpleUserManager.js create mode 100644 lib/user/v2/userManager/IListUserManager.d.ts create mode 100644 lib/user/v2/userManager/IListUserManager.js create mode 100644 lib/user/v2/userManager/ITestableUserManager.d.ts create mode 100644 lib/user/v2/userManager/ITestableUserManager.js create mode 100644 src/helper/v2/IfParser.ts create mode 100644 src/helper/v2/export.ts create mode 100644 src/manager/v2/Path.ts create mode 100644 src/manager/v2/export.ts create mode 100644 src/manager/v2/fileSystem/CommonTypes.ts create mode 100644 src/manager/v2/fileSystem/ContextInfo.ts create mode 100644 src/manager/v2/fileSystem/ContextualFileSystem.ts create mode 100644 src/manager/v2/fileSystem/FileSystem.ts create mode 100644 src/manager/v2/fileSystem/FileSystem.ts.old create mode 100644 src/manager/v2/fileSystem/LockManager.ts create mode 100644 src/manager/v2/fileSystem/PropertyManager.ts create mode 100644 src/manager/v2/fileSystem/Resource.ts create mode 100644 src/manager/v2/fileSystem/Serialization.ts create mode 100644 src/manager/v2/fileSystem/StandardMethods.ts create mode 100644 src/manager/v2/fileSystem/export.ts create mode 100644 src/manager/v2/instances/FTPFileSystem.ts create mode 100644 src/manager/v2/instances/PhysicalFileSystem.ts create mode 100644 src/manager/v2/instances/VirtualFileSystem.ts create mode 100644 src/resource/export.v2.ts create mode 100644 src/server/v2/RequestContext.ts create mode 100644 src/server/v2/WebDAVRequest.ts create mode 100644 src/server/v2/WebDAVServerOptions.ts create mode 100644 src/server/v2/commands/Commands.ts create mode 100644 src/server/v2/commands/Copy.ts create mode 100644 src/server/v2/commands/Delete.ts create mode 100644 src/server/v2/commands/Get.ts create mode 100644 src/server/v2/commands/Head.ts create mode 100644 src/server/v2/commands/Mkcol.ts create mode 100644 src/server/v2/commands/Move.ts create mode 100644 src/server/v2/commands/NotImplemented.ts create mode 100644 src/server/v2/commands/Options.ts create mode 100644 src/server/v2/commands/Post.ts create mode 100644 src/server/v2/commands/Propfind.ts create mode 100644 src/server/v2/commands/Proppatch.ts create mode 100644 src/server/v2/commands/Put.ts create mode 100644 src/server/v2/commands/Unlock.ts create mode 100644 src/server/v2/export.ts create mode 100644 src/server/v2/webDAVServer/BeforeAfter.ts create mode 100644 src/server/v2/webDAVServer/Events.ts create mode 100644 src/server/v2/webDAVServer/Persistence.ts create mode 100644 src/server/v2/webDAVServer/StartStop.ts create mode 100644 src/server/v2/webDAVServer/Types.ts create mode 100644 src/server/v2/webDAVServer/WebDAVServer.ts create mode 100644 src/user/v2/IUser.ts create mode 100644 src/user/v2/IUserManager.ts create mode 100644 src/user/v2/authentication/HTTPAuthentication.ts create mode 100644 src/user/v2/authentication/HTTPBasicAuthentication.ts create mode 100644 src/user/v2/authentication/HTTPDigestAuthentication.ts create mode 100644 src/user/v2/export.ts create mode 100644 src/user/v2/privilege/FakePrivilegeManager.ts create mode 100644 src/user/v2/privilege/IPrivilegeManager.ts create mode 100644 src/user/v2/privilege/SimplePathPrivilegeManager.ts create mode 100644 src/user/v2/privilege/SimplePrivilegeManager.ts create mode 100644 src/user/v2/simple/SimpleUser.ts create mode 100644 src/user/v2/simple/SimpleUserManager.ts create mode 100644 src/user/v2/userManager/IListUserManager.ts create mode 100644 src/user/v2/userManager/ITestableUserManager.ts diff --git a/lib/helper/v2/IfParser.d.ts b/lib/helper/v2/IfParser.d.ts new file mode 100644 index 00000000..6eb8f203 --- /dev/null +++ b/lib/helper/v2/IfParser.d.ts @@ -0,0 +1,5 @@ +import { ReturnCallback } from '../../resource/IResource'; +import { RequestContext } from '../../server/v2/RequestContext'; +import { Resource } from '../../manager/v2/fileSystem/Resource'; +export declare function extractOneToken(ifHeader: string): string; +export declare function parseIfHeader(ifHeader: string): (ctx: RequestContext, resource: Resource, callback: ReturnCallback) => void; diff --git a/lib/helper/v2/IfParser.js b/lib/helper/v2/IfParser.js new file mode 100644 index 00000000..dc4f22a2 --- /dev/null +++ b/lib/helper/v2/IfParser.js @@ -0,0 +1,134 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Path_1 = require("../../manager/v2/Path"); +var url = require("url"); +function NoLock() { + return function (resource, callback) { + resource.lockManager(function (e, lm) { + if (e) + return callback(e, false); + lm.getLocks(function (e, locks) { + callback(e, locks ? locks.length === 0 : false); + }); + }); + }; +} +function Token(token) { + return function (resource, callback) { + resource.lockManager(function (e, lm) { + if (e) + return callback(e, false); + lm.getLock(token, function (e, lock) { return callback(e, !!lock && !e); }); + }); + }; +} +function Tag(tag) { + return function (resource, callback) { + resource.etag(function (e, etag) { return callback(e, !e && etag === tag); }); + }; +} +function Not(filter) { + return function (resource, callback) { + filter(resource, function (e, v) { + callback(e, !v); + }); + }; +} +function parseInternal(group) { + var rex = /((not)|<([^>]+)>|\[([^\]]+)\]|<(DAV:no-lock)>)/ig; + var match = rex.exec(group); + var isNot = false; + var andArray = []; + function add(filter) { + andArray.push(isNot ? Not(filter) : filter); + isNot = false; + } + while (match) { + if (match[2]) { + isNot = true; + } + else if (match[3]) { + add(Token(match[3])); + } + else if (match[4]) { + add(Tag(match[4])); + } + else if (match[5]) { + add(NoLock()); + } + match = rex.exec(group); + } + if (andArray.length) + return function (r, callback) { return callback(null, true); }; + return function (resource, callback) { + var nb = andArray.length; + function done(error, result) { + if (nb <= 0) + return; + if (error) { + nb = -1; + callback(error, false); + return; + } + --nb; + if (nb === 0 || !result) { + nb = -1; + callback(null, result); + } + } + andArray.forEach(function (a) { return a(resource, done); }); + }; +} +function extractOneToken(ifHeader) { + var match = /^[ ]*\([ ]*<([^>]+)>[ ]*\)[ ]*$/.exec(ifHeader); + if (!match) + return null; + else + return match[1]; +} +exports.extractOneToken = extractOneToken; +function parseIfHeader(ifHeader) { + var rex = /(?:<([^>]+)>)?\s*\(([^\)]+)\)/g; + var match = rex.exec(ifHeader); + var orArray = []; + var oldPath = undefined; + while (match) { + if (match[1]) + oldPath = url.parse(match[1]).path; + orArray.push({ + path: oldPath, + actions: parseInternal(match[2]) + }); + match = rex.exec(ifHeader); + } + if (orArray.length == 0) + return function (ctx, resource, callback) { return callback(null, true); }; + return function (ctx, resource, callback) { + var nb = orArray.length; + function done(error, result) { + if (nb <= 0) + return; + if (error) { + nb = -1; + callback(error, false); + return; + } + --nb; + if (nb === 0 || result) { + nb = -1; + callback(null, result); + } + } + orArray.forEach(function (a) { + if (!a.path) + a.actions(resource, done); + else { + var sPath_1 = new Path_1.Path(a.path); + ctx.server.getFileSystem(sPath_1, function (fs, _, sub) { + a.actions(fs.resource(ctx, sPath_1), done); + }); + } + }); + }; +} +exports.parseIfHeader = parseIfHeader; diff --git a/lib/helper/v2/export.d.ts b/lib/helper/v2/export.d.ts new file mode 100644 index 00000000..5f2f1bb1 --- /dev/null +++ b/lib/helper/v2/export.d.ts @@ -0,0 +1,3 @@ +export * from './IfParser'; +export * from '../Workflow'; +export * from '../XML'; diff --git a/lib/helper/v2/export.js b/lib/helper/v2/export.js new file mode 100644 index 00000000..25bb373a --- /dev/null +++ b/lib/helper/v2/export.js @@ -0,0 +1,8 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./IfParser")); +__export(require("../Workflow")); +__export(require("../XML")); diff --git a/lib/manager/export.v2.d.ts b/lib/manager/export.v2.d.ts new file mode 100644 index 00000000..4f00993c --- /dev/null +++ b/lib/manager/export.v2.d.ts @@ -0,0 +1 @@ +export * from './export'; diff --git a/lib/manager/export.v2.js b/lib/manager/export.v2.js new file mode 100644 index 00000000..e4065b10 --- /dev/null +++ b/lib/manager/export.v2.js @@ -0,0 +1,6 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./export")); diff --git a/lib/manager/v2/Path.d.ts b/lib/manager/v2/Path.d.ts new file mode 100644 index 00000000..113495b6 --- /dev/null +++ b/lib/manager/v2/Path.d.ts @@ -0,0 +1,15 @@ +export declare class Path { + paths: string[]; + constructor(path: Path | string[] | string); + isRoot(): boolean; + fileName(): string; + rootName(): string; + parentName(): string; + getParent(): Path; + hasParent(): boolean; + removeRoot(): string; + removeFile(): string; + getChildPath(childPath: string | Path): Path; + clone(): Path; + toString(endsWithSlash?: boolean): string; +} diff --git a/lib/manager/v2/Path.js b/lib/manager/v2/Path.js new file mode 100644 index 00000000..5bb00816 --- /dev/null +++ b/lib/manager/v2/Path.js @@ -0,0 +1,63 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Path = (function () { + function Path(path) { + if (path.constructor === String) { + var sPath = decodeURI(path); + var doubleIndex = void 0; + while ((doubleIndex = sPath.indexOf('//')) !== -1) + sPath = sPath.substr(0, doubleIndex) + sPath.substr(doubleIndex + 1); + this.paths = sPath.replace(/(^\/|\/$)/g, '').split('/'); + } + else if (path.constructor === Path) + this.paths = path.paths.filter(function (x) { return true; }); // clone + else + this.paths = path; + this.paths = this.paths.filter(function (p) { return p.length > 0; }); + } + Path.prototype.isRoot = function () { + return this.paths.length === 0 || this.paths.length === 1 && this.paths[0].length === 0; + }; + Path.prototype.fileName = function () { + return this.paths[this.paths.length - 1]; + }; + Path.prototype.rootName = function () { + return this.paths[0]; + }; + Path.prototype.parentName = function () { + return this.paths[this.paths.length - 2]; + }; + Path.prototype.getParent = function () { + return new Path(this.paths.slice(0, this.paths.length - 1)); + }; + Path.prototype.hasParent = function () { + return this.paths.length >= 2; + }; + Path.prototype.removeRoot = function () { + return this.paths.shift(); + }; + Path.prototype.removeFile = function () { + return this.paths.pop(); + }; + Path.prototype.getChildPath = function (childPath) { + var subPath = new Path(childPath); + var path = this.clone(); + for (var _i = 0, _a = subPath.paths; _i < _a.length; _i++) { + var subName = _a[_i]; + path.paths.push(subName); + } + return path; + }; + Path.prototype.clone = function () { + return new Path(this); + }; + Path.prototype.toString = function (endsWithSlash) { + if (endsWithSlash === void 0) { endsWithSlash = false; } + var value = '/' + this.paths.join('/'); + if (endsWithSlash && value.length > 1) + return value + '/'; + return value; + }; + return Path; +}()); +exports.Path = Path; diff --git a/lib/manager/v2/export.d.ts b/lib/manager/v2/export.d.ts new file mode 100644 index 00000000..83732768 --- /dev/null +++ b/lib/manager/v2/export.d.ts @@ -0,0 +1,5 @@ +export * from './instances/FTPFileSystem'; +export * from './instances/PhysicalFileSystem'; +export * from './instances/VirtualFileSystem'; +export * from './fileSystem/export'; +export * from './Path'; diff --git a/lib/manager/v2/export.js b/lib/manager/v2/export.js new file mode 100644 index 00000000..c609fb33 --- /dev/null +++ b/lib/manager/v2/export.js @@ -0,0 +1,10 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./instances/FTPFileSystem")); +__export(require("./instances/PhysicalFileSystem")); +__export(require("./instances/VirtualFileSystem")); +__export(require("./fileSystem/export")); +__export(require("./Path")); diff --git a/lib/manager/v2/fileSystem/CommonTypes.d.ts b/lib/manager/v2/fileSystem/CommonTypes.d.ts new file mode 100644 index 00000000..37f98ea8 --- /dev/null +++ b/lib/manager/v2/fileSystem/CommonTypes.d.ts @@ -0,0 +1,18 @@ +import { XMLElement } from '../../../helper/XML'; +export declare type SimpleCallback = (error?: Error) => void; +export declare type ReturnCallback = (error?: Error, data?: T) => void; +export declare type Return2Callback = (error?: Error, data1?: T1, data2?: T2) => void; +export declare type ResourcePropertyValue = string | XMLElement | XMLElement[]; +export declare class ResourceType { + isFile: boolean; + isDirectory: boolean; + static File: ResourceType; + static Directory: ResourceType; + static Hybrid: ResourceType; + static NoResource: ResourceType; + constructor(isFile: boolean, isDirectory: boolean); +} +export declare type OpenWriteStreamMode = 'mustCreate' | 'canCreate' | 'mustExist' | 'canCreateIntermediates' | 'mustCreateIntermediates'; +export interface SubTree { + [name: string]: ResourceType | SubTree; +} diff --git a/lib/manager/v2/fileSystem/CommonTypes.js b/lib/manager/v2/fileSystem/CommonTypes.js new file mode 100644 index 00000000..33a9c4c8 --- /dev/null +++ b/lib/manager/v2/fileSystem/CommonTypes.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var ResourceType = (function () { + function ResourceType(isFile, isDirectory) { + this.isFile = isFile; + this.isDirectory = isDirectory; + } + return ResourceType; +}()); +ResourceType.File = new ResourceType(true, false); +ResourceType.Directory = new ResourceType(false, true); +ResourceType.Hybrid = new ResourceType(true, true); +ResourceType.NoResource = new ResourceType(false, false); +exports.ResourceType = ResourceType; diff --git a/lib/manager/v2/fileSystem/ContextInfo.d.ts b/lib/manager/v2/fileSystem/ContextInfo.d.ts new file mode 100644 index 00000000..e81ef158 --- /dev/null +++ b/lib/manager/v2/fileSystem/ContextInfo.d.ts @@ -0,0 +1,55 @@ +import { RequestContext } from '../../../server/v2/RequestContext'; +import { ResourceType, OpenWriteStreamMode } from './CommonTypes'; +export interface IContextInfo { + context: RequestContext; +} +export interface OpenWriteStreamInfo extends IContextInfo { + targetSource: boolean; + estimatedSize: number; + mode: OpenWriteStreamMode; +} +export interface OpenReadStreamInfo extends IContextInfo { + targetSource: boolean; + estimatedSize: number; +} +export interface MimeTypeInfo extends IContextInfo { + targetSource: boolean; +} +export interface SizeInfo extends IContextInfo { + targetSource: boolean; +} +export interface CreateInfo extends IContextInfo { + type: ResourceType; +} +export interface CopyInfo extends IContextInfo { + depth: number; + overwrite: boolean; +} +export interface DeleteInfo extends IContextInfo { + depth: number; +} +export interface MoveInfo extends IContextInfo { + overwrite: boolean; +} +export interface ETagInfo extends IContextInfo { +} +export interface RenameInfo extends IContextInfo { +} +export interface AvailableLocksInfo extends IContextInfo { +} +export interface LockManagerInfo extends IContextInfo { +} +export interface PropertyManagerInfo extends IContextInfo { +} +export interface ReadDirInfo extends IContextInfo { +} +export interface CreationDateInfo extends IContextInfo { +} +export interface LastModifiedDateInfo extends IContextInfo { +} +export interface WebNameInfo extends IContextInfo { +} +export interface DisplayNameInfo extends IContextInfo { +} +export interface TypeInfo extends IContextInfo { +} diff --git a/lib/manager/v2/fileSystem/ContextInfo.js b/lib/manager/v2/fileSystem/ContextInfo.js new file mode 100644 index 00000000..c8ad2e54 --- /dev/null +++ b/lib/manager/v2/fileSystem/ContextInfo.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/manager/v2/fileSystem/ContextualFileSystem.d.ts b/lib/manager/v2/fileSystem/ContextualFileSystem.d.ts new file mode 100644 index 00000000..74ddde70 --- /dev/null +++ b/lib/manager/v2/fileSystem/ContextualFileSystem.d.ts @@ -0,0 +1,67 @@ +/// +import { Readable, Writable } from 'stream'; +import { RequestContext } from '../../../server/v2/RequestContext'; +import { LockKind } from '../../../resource/lock/LockKind'; +import { Lock } from '../../../resource/lock/Lock'; +import { Path } from '../Path'; +import { ReturnCallback, SimpleCallback, Return2Callback, OpenWriteStreamMode, SubTree, ResourceType } from './CommonTypes'; +import { FileSystemSerializer, ISerializableFileSystem } from './Serialization'; +import { FileSystem } from './FileSystem'; +import { Resource } from './Resource'; +import { IPropertyManager } from './PropertyManager'; +import { ILockManager } from './LockManager'; +export declare class ContextualFileSystem implements ISerializableFileSystem { + fs: FileSystem; + context: RequestContext; + constructor(fs: FileSystem, context: RequestContext); + resource(path: Path): Resource; + delete(path: Path, callback: SimpleCallback): void; + delete(path: Path, depth: number, callback: SimpleCallback): void; + openWriteStream(path: Path, callback: Return2Callback): void; + openWriteStream(path: Path, estimatedSize: number, callback: Return2Callback): void; + openWriteStream(path: Path, targetSource: boolean, callback: Return2Callback): void; + openWriteStream(path: Path, targetSource: boolean, estimatedSize: number, callback: Return2Callback): void; + openWriteStream(path: Path, mode: OpenWriteStreamMode, callback: Return2Callback): void; + openWriteStream(path: Path, mode: OpenWriteStreamMode, estimatedSize: number, callback: Return2Callback): void; + openWriteStream(path: Path, mode: OpenWriteStreamMode, targetSource: boolean, callback: Return2Callback): void; + openWriteStream(path: Path, mode: OpenWriteStreamMode, targetSource: boolean, estimatedSize: number, callback: Return2Callback): void; + openReadStream(path: Path, callback: ReturnCallback): void; + openReadStream(path: Path, estimatedSize: number, callback: ReturnCallback): void; + openReadStream(path: Path, targetSource: boolean, callback: ReturnCallback): void; + openReadStream(path: Path, targetSource: boolean, estimatedSize: number, callback: ReturnCallback): void; + copy(pathFrom: Path, pathTo: Path, callback: ReturnCallback): void; + copy(pathFrom: Path, pathTo: Path, depth: number, callback: ReturnCallback): void; + copy(pathFrom: Path, pathTo: Path, overwrite: boolean, callback: ReturnCallback): void; + copy(pathFrom: Path, pathTo: Path, overwrite: boolean, depth: number, callback: ReturnCallback): void; + mimeType(path: Path, callback: ReturnCallback): void; + mimeType(path: Path, targetSource: boolean, callback: ReturnCallback): void; + size(path: Path, callback: ReturnCallback): void; + size(path: Path, targetSource: boolean, callback: ReturnCallback): void; + addSubTree(rootPath: Path, subTree: SubTree, callback: SimpleCallback): any; + addSubTree(rootPath: Path, resourceType: ResourceType, callback: SimpleCallback): any; + create(path: Path, type: ResourceType, callback: SimpleCallback): void; + create(path: Path, type: ResourceType, createIntermediates: boolean, callback: SimpleCallback): void; + etag(path: Path, callback: ReturnCallback): void; + move(pathFrom: Path, pathTo: Path, callback: ReturnCallback): void; + move(pathFrom: Path, pathTo: Path, overwrite: boolean, callback: ReturnCallback): void; + rename(pathFrom: Path, newName: string, callback: ReturnCallback): void; + rename(pathFrom: Path, newName: string, overwrite: boolean, callback: ReturnCallback): void; + availableLocks(path: Path, callback: ReturnCallback): void; + lockManager(path: Path, callback: ReturnCallback): void; + propertyManager(path: Path, callback: ReturnCallback): void; + readDir(path: Path, callback: ReturnCallback): void; + readDir(path: Path, retrieveExternalFiles: boolean, callback: ReturnCallback): void; + creationDate(path: Path, callback: ReturnCallback): void; + lastModifiedDate(path: Path, callback: ReturnCallback): void; + webName(path: Path, callback: ReturnCallback): void; + displayName(path: Path, callback: ReturnCallback): void; + type(path: Path, callback: ReturnCallback): void; + listDeepLocks(startPath: Path, callback: ReturnCallback<{ + [path: string]: Lock[]; + }>): any; + listDeepLocks(startPath: Path, depth: number, callback: ReturnCallback<{ + [path: string]: Lock[]; + }>): any; + serializer(): FileSystemSerializer; + serialize(callback: (serializedData: any) => void): void; +} diff --git a/lib/manager/v2/fileSystem/ContextualFileSystem.js b/lib/manager/v2/fileSystem/ContextualFileSystem.js new file mode 100644 index 00000000..3b739626 --- /dev/null +++ b/lib/manager/v2/fileSystem/ContextualFileSystem.js @@ -0,0 +1,83 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Resource_1 = require("./Resource"); +var ContextualFileSystem = (function () { + function ContextualFileSystem(fs, context) { + this.fs = fs; + this.context = context; + } + ContextualFileSystem.prototype.resource = function (path) { + return new Resource_1.Resource(path, this.fs, this.context); + }; + ContextualFileSystem.prototype.delete = function (path, _depth, _callback) { + this.fs.delete(this.context, path, _depth, _callback); + }; + ContextualFileSystem.prototype.openWriteStream = function (path, _mode, _targetSource, _estimatedSize, _callback) { + this.fs.openWriteStream(this.context, path, _mode, _targetSource, _estimatedSize, _callback); + }; + ContextualFileSystem.prototype.openReadStream = function (path, _targetSource, _estimatedSize, _callback) { + this.fs.openReadStream(this.context, path, _targetSource, _estimatedSize, _callback); + }; + ContextualFileSystem.prototype.copy = function (pathFrom, pathTo, _overwrite, _depth, _callback) { + this.fs.copy(this.context, pathFrom, pathTo, _overwrite, _depth, _callback); + }; + ContextualFileSystem.prototype.mimeType = function (path, _targetSource, _callback) { + this.fs.mimeType(this.context, path, _targetSource, _callback); + }; + ContextualFileSystem.prototype.size = function (path, _targetSource, _callback) { + this.fs.size(this.context, path, _targetSource, _callback); + }; + ContextualFileSystem.prototype.addSubTree = function (rootPath, tree, callback) { + this.fs.size(this.context, rootPath, tree, callback); + }; + ContextualFileSystem.prototype.create = function (path, type, _createIntermediates, _callback) { + this.fs.create(this.context, path, type, _createIntermediates, _callback); + }; + ContextualFileSystem.prototype.etag = function (path, callback) { + this.fs.etag(this.context, path, callback); + }; + ContextualFileSystem.prototype.move = function (pathFrom, pathTo, _overwrite, _callback) { + this.fs.move(this.context, pathFrom, pathTo, _overwrite, _callback); + }; + ContextualFileSystem.prototype.rename = function (pathFrom, newName, _overwrite, _callback) { + this.fs.rename(this.context, pathFrom, newName, _overwrite, _callback); + }; + ContextualFileSystem.prototype.availableLocks = function (path, callback) { + this.fs.availableLocks(this.context, path, callback); + }; + ContextualFileSystem.prototype.lockManager = function (path, callback) { + this.fs.lockManager(this.context, path, callback); + }; + ContextualFileSystem.prototype.propertyManager = function (path, callback) { + this.fs.propertyManager(this.context, path, callback); + }; + ContextualFileSystem.prototype.readDir = function (path, _retrieveExternalFiles, _callback) { + this.fs.readDir(this.context, path, _retrieveExternalFiles, _callback); + }; + ContextualFileSystem.prototype.creationDate = function (path, callback) { + this.fs.creationDate(this.context, path, callback); + }; + ContextualFileSystem.prototype.lastModifiedDate = function (path, callback) { + this.fs.lastModifiedDate(this.context, path, callback); + }; + ContextualFileSystem.prototype.webName = function (path, callback) { + this.fs.webName(this.context, path, callback); + }; + ContextualFileSystem.prototype.displayName = function (path, callback) { + this.fs.displayName(this.context, path, callback); + }; + ContextualFileSystem.prototype.type = function (path, callback) { + this.fs.type(this.context, path, callback); + }; + ContextualFileSystem.prototype.listDeepLocks = function (startPath, _depth, _callback) { + this.fs.listDeepLocks(this.context, startPath, _depth, _callback); + }; + ContextualFileSystem.prototype.serializer = function () { + return this.fs.serializer(); + }; + ContextualFileSystem.prototype.serialize = function (callback) { + this.fs.serialize(callback); + }; + return ContextualFileSystem; +}()); +exports.ContextualFileSystem = ContextualFileSystem; diff --git a/lib/manager/v2/fileSystem/FileSystem.d.ts b/lib/manager/v2/fileSystem/FileSystem.d.ts new file mode 100644 index 00000000..593eb7df --- /dev/null +++ b/lib/manager/v2/fileSystem/FileSystem.d.ts @@ -0,0 +1,99 @@ +/// +import { AvailableLocksInfo, CopyInfo, CreateInfo, CreationDateInfo, DeleteInfo, DisplayNameInfo, ETagInfo, LastModifiedDateInfo, LockManagerInfo, MimeTypeInfo, MoveInfo, OpenReadStreamInfo, OpenWriteStreamInfo, PropertyManagerInfo, ReadDirInfo, RenameInfo, SizeInfo, TypeInfo } from './ContextInfo'; +import { Readable, Writable } from 'stream'; +import { RequestContext } from '../../../server/v2/RequestContext'; +import { BasicPrivilege } from '../../../user/v2/privilege/IPrivilegeManager'; +import { LockKind } from '../../../resource/lock/LockKind'; +import { Lock } from '../../../resource/lock/Lock'; +import { Path } from '../Path'; +import { ResourceType, SimpleCallback, Return2Callback, ReturnCallback, SubTree, OpenWriteStreamMode } from './CommonTypes'; +import { ContextualFileSystem } from './ContextualFileSystem'; +import { ILockManager } from './LockManager'; +import { IPropertyManager } from './PropertyManager'; +import { Resource } from './Resource'; +import { ISerializableFileSystem, FileSystemSerializer } from './Serialization'; +export declare abstract class FileSystem implements ISerializableFileSystem { + private __serializer; + constructor(serializer: FileSystemSerializer); + serializer(): FileSystemSerializer; + contextualize(ctx: RequestContext): ContextualFileSystem; + resource(ctx: RequestContext, path: Path): Resource; + fastExistCheckEx(ctx: RequestContext, path: Path, errorCallback: SimpleCallback, callback: () => void): void; + fastExistCheckExReverse(ctx: RequestContext, path: Path, errorCallback: SimpleCallback, callback: () => void): void; + protected fastExistCheck(ctx: RequestContext, path: Path, callback: (exists: boolean) => void): void; + protected _fastExistCheck?(ctx: RequestContext, path: Path, callback: (exists: boolean) => void): void; + create(ctx: RequestContext, path: Path, type: ResourceType, callback: SimpleCallback): void; + create(ctx: RequestContext, path: Path, type: ResourceType, createIntermediates: boolean, callback: SimpleCallback): void; + protected _create?(path: Path, ctx: CreateInfo, callback: SimpleCallback): void; + etag(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + protected _etag?(path: Path, ctx: ETagInfo, callback: ReturnCallback): void; + delete(ctx: RequestContext, path: Path, callback: SimpleCallback): void; + delete(ctx: RequestContext, path: Path, depth: number, callback: SimpleCallback): void; + protected _delete?(path: Path, ctx: DeleteInfo, callback: SimpleCallback): void; + openWriteStream(ctx: RequestContext, path: Path, callback: Return2Callback): void; + openWriteStream(ctx: RequestContext, path: Path, estimatedSize: number, callback: Return2Callback): void; + openWriteStream(ctx: RequestContext, path: Path, targetSource: boolean, callback: Return2Callback): void; + openWriteStream(ctx: RequestContext, path: Path, targetSource: boolean, estimatedSize: number, callback: Return2Callback): void; + openWriteStream(ctx: RequestContext, path: Path, mode: OpenWriteStreamMode, callback: Return2Callback): void; + openWriteStream(ctx: RequestContext, path: Path, mode: OpenWriteStreamMode, estimatedSize: number, callback: Return2Callback): void; + openWriteStream(ctx: RequestContext, path: Path, mode: OpenWriteStreamMode, targetSource: boolean, callback: Return2Callback): void; + openWriteStream(ctx: RequestContext, path: Path, mode: OpenWriteStreamMode, targetSource: boolean, estimatedSize: number, callback: Return2Callback): void; + protected _openWriteStream?(path: Path, ctx: OpenWriteStreamInfo, callback: ReturnCallback): void; + openReadStream(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + openReadStream(ctx: RequestContext, path: Path, estimatedSize: number, callback: ReturnCallback): void; + openReadStream(ctx: RequestContext, path: Path, targetSource: boolean, callback: ReturnCallback): void; + openReadStream(ctx: RequestContext, path: Path, targetSource: boolean, estimatedSize: number, callback: ReturnCallback): void; + protected _openReadStream?(path: Path, ctx: OpenReadStreamInfo, callback: ReturnCallback): void; + move(ctx: RequestContext, pathFrom: Path, pathTo: Path, callback: ReturnCallback): void; + move(ctx: RequestContext, pathFrom: Path, pathTo: Path, overwrite: boolean, callback: ReturnCallback): void; + protected _move?(pathFrom: Path, pathTo: Path, ctx: MoveInfo, callback: ReturnCallback): void; + copy(ctx: RequestContext, pathFrom: Path, pathTo: Path, callback: ReturnCallback): void; + copy(ctx: RequestContext, pathFrom: Path, pathTo: Path, depth: number, callback: ReturnCallback): void; + copy(ctx: RequestContext, pathFrom: Path, pathTo: Path, overwrite: boolean, callback: ReturnCallback): void; + copy(ctx: RequestContext, pathFrom: Path, pathTo: Path, overwrite: boolean, depth: number, callback: ReturnCallback): void; + protected _copy?(pathFrom: Path, pathTo: Path, ctx: CopyInfo, callback: ReturnCallback): void; + rename(ctx: RequestContext, pathFrom: Path, newName: string, callback: ReturnCallback): void; + rename(ctx: RequestContext, pathFrom: Path, newName: string, overwrite: boolean, callback: ReturnCallback): void; + protected _rename?(pathFrom: Path, newName: string, ctx: RenameInfo, callback: ReturnCallback): void; + mimeType(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + mimeType(ctx: RequestContext, path: Path, targetSource: boolean, callback: ReturnCallback): void; + protected _mimeType?(path: Path, ctx: MimeTypeInfo, callback: ReturnCallback): void; + size(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + size(ctx: RequestContext, path: Path, targetSource: boolean, callback: ReturnCallback): void; + protected _size?(path: Path, ctx: SizeInfo, callback: ReturnCallback): void; + availableLocks(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + protected _availableLocks?(path: Path, ctx: AvailableLocksInfo, callback: ReturnCallback): void; + lockManager(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + protected abstract _lockManager(path: Path, ctx: LockManagerInfo, callback: ReturnCallback): void; + propertyManager(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + protected abstract _propertyManager(path: Path, ctx: PropertyManagerInfo, callback: ReturnCallback): void; + readDir(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + readDir(ctx: RequestContext, path: Path, retrieveExternalFiles: boolean, callback: ReturnCallback): void; + protected _readDir?(path: Path, ctx: ReadDirInfo, callback: ReturnCallback): void; + creationDate(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + protected _creationDate?(path: Path, ctx: CreationDateInfo, callback: ReturnCallback): void; + lastModifiedDate(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + protected _lastModifiedDate?(path: Path, ctx: LastModifiedDateInfo, callback: ReturnCallback): void; + webName(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + displayName(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + protected _displayName?(path: Path, ctx: DisplayNameInfo, callback: ReturnCallback): void; + type(ctx: RequestContext, path: Path, callback: ReturnCallback): void; + protected abstract _type(path: Path, ctx: TypeInfo, callback: ReturnCallback): void; + addSubTree(ctx: RequestContext, subTree: SubTree, callback: SimpleCallback): any; + addSubTree(ctx: RequestContext, resourceType: ResourceType, callback: SimpleCallback): any; + addSubTree(ctx: RequestContext, rootPath: Path, subTree: SubTree, callback: SimpleCallback): any; + addSubTree(ctx: RequestContext, rootPath: Path, resourceType: ResourceType, callback: SimpleCallback): any; + listDeepLocks(ctx: RequestContext, startPath: Path, callback: ReturnCallback<{ + [path: string]: Lock[]; + }>): any; + listDeepLocks(ctx: RequestContext, startPath: Path, depth: number, callback: ReturnCallback<{ + [path: string]: Lock[]; + }>): any; + getFullPath(ctx: RequestContext, callback: ReturnCallback): any; + getFullPath(ctx: RequestContext, path: Path, callback: ReturnCallback): any; + checkPrivilege(ctx: RequestContext, path: Path, privilege: BasicPrivilege, callback: ReturnCallback): any; + checkPrivilege(ctx: RequestContext, path: Path, privileges: BasicPrivilege[], callback: ReturnCallback): any; + checkPrivilege(ctx: RequestContext, path: Path, privilege: string, callback: ReturnCallback): any; + checkPrivilege(ctx: RequestContext, path: Path, privileges: string[], callback: ReturnCallback): any; + serialize(callback: ReturnCallback): void; +} diff --git a/lib/manager/v2/fileSystem/FileSystem.js b/lib/manager/v2/fileSystem/FileSystem.js new file mode 100644 index 00000000..0be393a4 --- /dev/null +++ b/lib/manager/v2/fileSystem/FileSystem.js @@ -0,0 +1,515 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var LockScope_1 = require("../../../resource/lock/LockScope"); +var LockType_1 = require("../../../resource/lock/LockType"); +var LockKind_1 = require("../../../resource/lock/LockKind"); +var Workflow_1 = require("../../../helper/Workflow"); +var Errors_1 = require("../../../Errors"); +var Path_1 = require("../Path"); +var CommonTypes_1 = require("./CommonTypes"); +var ContextualFileSystem_1 = require("./ContextualFileSystem"); +var Resource_1 = require("./Resource"); +var StandardMethods_1 = require("./StandardMethods"); +var crypto = require("crypto"); +var FileSystem = (function () { + function FileSystem(serializer) { + this.__serializer = serializer; + } + FileSystem.prototype.serializer = function () { + return this.__serializer; + }; + FileSystem.prototype.contextualize = function (ctx) { + return new ContextualFileSystem_1.ContextualFileSystem(this, ctx); + }; + FileSystem.prototype.resource = function (ctx, path) { + return new Resource_1.Resource(path, this, ctx); + }; + FileSystem.prototype.fastExistCheckEx = function (ctx, path, errorCallback, callback) { + if (!this._fastExistCheck) + return callback(); + this._fastExistCheck(ctx, path, function (exists) { + if (!exists) + errorCallback(Errors_1.Errors.ResourceNotFound); + else + callback(); + }); + }; + FileSystem.prototype.fastExistCheckExReverse = function (ctx, path, errorCallback, callback) { + if (!this._fastExistCheck) + return callback(); + this._fastExistCheck(ctx, path, function (exists) { + if (exists) + errorCallback(Errors_1.Errors.ResourceAlreadyExists); + else + callback(); + }); + }; + FileSystem.prototype.fastExistCheck = function (ctx, path, callback) { + if (!this._fastExistCheck) + return callback(true); + this._fastExistCheck(ctx, path, function (exists) { return callback(!!exists); }); + }; + FileSystem.prototype.create = function (ctx, path, type, _createIntermediates, _callback) { + var _this = this; + var createIntermediates = _callback ? _createIntermediates : false; + var callback = _callback ? _callback : _createIntermediates; + if (!this._create) + return callback(Errors_1.Errors.InvalidOperation); + var go = function () { + _this._create(path, { + context: ctx, + type: type + }, callback); + }; + this.fastExistCheckExReverse(ctx, path, callback, function () { + _this.type(ctx, path.getParent(), function (e, type) { + if (e === Errors_1.Errors.ResourceNotFound) { + if (!createIntermediates) + return callback(Errors_1.Errors.IntermediateResourceMissing); + _this.getFullPath(ctx, path, function (e, fullPath) { + if (e) + return callback(e); + fullPath = fullPath.getParent(); + ctx.getResource(fullPath, function (e, r) { + if (e) + return callback(e); + r.create(CommonTypes_1.ResourceType.Directory, function (e) { + if (e && e !== Errors_1.Errors.ResourceAlreadyExists) + return callback(e); + go(); + }); + }); + }); + return; + } + if (e) + return callback(e); + if (!type.isDirectory) + return callback(Errors_1.Errors.WrongParentTypeForCreation); + go(); + }); + }); + }; + FileSystem.prototype.etag = function (ctx, path, callback) { + var _this = this; + this.fastExistCheckEx(ctx, path, callback, function () { + if (!_this._etag) + return _this.lastModifiedDate(ctx, path, function (e, date) { + if (e) + return callback(e); + callback(null, '"' + crypto.createHash('md5').update(date.toString()).digest('hex') + '"'); + }); + _this._etag(path, { + context: ctx + }, callback); + }); + }; + FileSystem.prototype.delete = function (ctx, path, _depth, _callback) { + var _this = this; + var depth = _callback ? _depth : -1; + var callback = _callback ? _callback : _depth; + if (!this._delete) + return callback(Errors_1.Errors.InvalidOperation); + this.fastExistCheckEx(ctx, path, callback, function () { + _this._delete(path, { + context: ctx, + depth: depth + }, callback); + }); + }; + FileSystem.prototype.openWriteStream = function (ctx, path, _mode, _targetSource, _estimatedSize, _callback) { + var _this = this; + var targetSource = true; + for (var _i = 0, _a = [_mode, _targetSource]; _i < _a.length; _i++) { + var obj = _a[_i]; + if (obj && obj.constructor === Boolean) + targetSource = obj; + } + var estimatedSize = -1; + for (var _b = 0, _c = [_mode, _targetSource, _estimatedSize]; _b < _c.length; _b++) { + var obj = _c[_b]; + if (obj && obj.constructor === Number) + estimatedSize = obj; + } + var callback; + for (var _d = 0, _e = [_mode, _targetSource, _estimatedSize, _callback]; _d < _e.length; _d++) { + var obj = _e[_d]; + if (obj && obj.constructor === Function) + callback = obj; + } + var mode = _mode && _mode.constructor === String ? _mode : 'mustExist'; + var created = false; + if (!this._openWriteStream) + return callback(Errors_1.Errors.InvalidOperation); + var go = function (callback) { + _this._openWriteStream(path, { + context: ctx, + estimatedSize: estimatedSize, + targetSource: targetSource, + mode: mode + }, function (e, wStream) { return callback(e, wStream, created); }); + }; + var createAndGo = function (intermediates) { + _this.create(ctx, path, CommonTypes_1.ResourceType.File, intermediates, function (e) { + if (e) + return callback(e); + created = true; + go(callback); + }); + }; + switch (mode) { + case 'mustExist': + this.fastExistCheckEx(ctx, path, callback, function () { return go(callback); }); + break; + case 'mustCreateIntermediates': + case 'mustCreate': + createAndGo(mode === 'mustCreateIntermediates'); + break; + case 'canCreateIntermediates': + case 'canCreate': + go(function (e, wStream) { + if (e === Errors_1.Errors.ResourceNotFound) + createAndGo(mode === 'canCreateIntermediates'); + else + callback(e, wStream); + }); + break; + default: + callback(Errors_1.Errors.IllegalArguments); + break; + } + }; + FileSystem.prototype.openReadStream = function (ctx, path, _targetSource, _estimatedSize, _callback) { + var _this = this; + var targetSource = _targetSource.constructor === Boolean ? _targetSource : true; + var estimatedSize = _callback ? _estimatedSize : _estimatedSize ? _targetSource : -1; + var callback = _callback ? _callback : _estimatedSize ? _estimatedSize : _targetSource; + this.fastExistCheckEx(ctx, path, callback, function () { + if (!_this._openReadStream) + return callback(Errors_1.Errors.InvalidOperation); + _this._openReadStream(path, { + context: ctx, + estimatedSize: estimatedSize, + targetSource: targetSource + }, callback); + }); + }; + FileSystem.prototype.move = function (ctx, pathFrom, pathTo, _overwrite, _callback) { + var _this = this; + var callback = _callback ? _callback : _overwrite; + var overwrite = _callback ? _overwrite : false; + var go = function () { + if (_this._move) { + _this._move(pathFrom, pathTo, { + context: ctx, + overwrite: overwrite + }, callback); + return; + } + StandardMethods_1.StandardMethods.standardMove(ctx, pathFrom, _this, pathTo, _this, callback); + }; + this.fastExistCheckEx(ctx, pathFrom, callback, function () { + if (!overwrite) + _this.fastExistCheckExReverse(ctx, pathTo, callback, go); + else + go(); + }); + }; + FileSystem.prototype.copy = function (ctx, pathFrom, pathTo, _overwrite, _depth, _callback) { + var _this = this; + var overwrite = _overwrite.constructor === Boolean ? _overwrite : false; + var depth = _callback ? _depth : !_depth ? -1 : _overwrite.constructor === Number ? _overwrite : -1; + var callback = _callback ? _callback : _depth ? _depth : _overwrite; + if (this._copy) { + var go_1 = function () { + _this._copy(pathFrom, pathTo, { + context: ctx, + depth: depth, + overwrite: overwrite + }, callback); + }; + this.fastExistCheckEx(ctx, pathFrom, callback, function () { + if (!overwrite) + _this.fastExistCheckExReverse(ctx, pathTo, callback, go_1); + else + go_1(); + }); + } + else + StandardMethods_1.StandardMethods.standardCopy(ctx, pathFrom, this, pathTo, this, overwrite, depth, callback); + }; + FileSystem.prototype.rename = function (ctx, pathFrom, newName, _overwrite, _callback) { + var _this = this; + var overwrite = _callback ? _overwrite : false; + var callback = _callback ? _callback : _overwrite; + if (pathFrom.isRoot()) { + this.getFullPath(ctx, function (e, fullPath) { + if (fullPath.isRoot()) + return callback(Errors_1.Errors.InvalidOperation); + var newPath = fullPath.getParent().getChildPath(newName); + ctx.server.getFileSystem(newPath, function (fs, _, subPath) { + var go = function (overwritten) { + ctx.server.setFileSystem(newPath, _this, function (successed) { + if (!successed) + return callback(Errors_1.Errors.InvalidOperation); + ctx.server.removeFileSystem(fullPath, function () { return callback(null, overwritten); }); + }); + }; + if (!subPath.isRoot()) + return go(false); + if (!overwrite) + return callback(Errors_1.Errors.ResourceAlreadyExists); + ctx.server.removeFileSystem(newPath, function () { + go(true); + }); + }); + }); + return; + } + this.fastExistCheckEx(ctx, pathFrom, callback, function () { + _this.fastExistCheckExReverse(ctx, pathFrom.getParent().getChildPath(newName), callback, function () { + if (_this._rename) { + _this._rename(pathFrom, newName, { + context: ctx + }, callback); + return; + } + _this.move(ctx, pathFrom, pathFrom.getParent().getChildPath(newName), overwrite, callback); + }); + }); + }; + FileSystem.prototype.mimeType = function (ctx, path, _targetSource, _callback) { + var _this = this; + var targetSource = _callback ? _targetSource : true; + var callback = _callback ? _callback : _targetSource; + this.fastExistCheckEx(ctx, path, callback, function () { + if (_this._mimeType) { + _this._mimeType(path, { + context: ctx, + targetSource: targetSource + }, callback); + return; + } + StandardMethods_1.StandardMethods.standardMimeType(ctx, _this, path, targetSource, callback); + }); + }; + FileSystem.prototype.size = function (ctx, path, _targetSource, _callback) { + var _this = this; + var targetSource = _callback ? _targetSource : true; + var callback = _callback ? _callback : _targetSource; + this.fastExistCheckEx(ctx, path, callback, function () { + if (!_this._size) + return callback(null, 0); + _this._size(path, { + context: ctx, + targetSource: targetSource + }, callback); + }); + }; + FileSystem.prototype.availableLocks = function (ctx, path, callback) { + var _this = this; + this.fastExistCheckEx(ctx, path, callback, function () { + if (!_this._availableLocks) + return callback(null, [ + new LockKind_1.LockKind(LockScope_1.LockScope.Exclusive, LockType_1.LockType.Write), + new LockKind_1.LockKind(LockScope_1.LockScope.Shared, LockType_1.LockType.Write) + ]); + _this._availableLocks(path, { + context: ctx + }, callback); + }); + }; + FileSystem.prototype.lockManager = function (ctx, path, callback) { + var _this = this; + this.fastExistCheckEx(ctx, path, callback, function () { + _this._lockManager(path, { + context: ctx + }, callback); + }); + }; + FileSystem.prototype.propertyManager = function (ctx, path, callback) { + var _this = this; + this.fastExistCheckEx(ctx, path, callback, function () { + _this._propertyManager(path, { + context: ctx + }, callback); + }); + }; + FileSystem.prototype.readDir = function (ctx, path, _retrieveExternalFiles, _callback) { + var _this = this; + var retrieveExternalFiles = _callback ? _retrieveExternalFiles : false; + var callback = _callback ? _callback : _retrieveExternalFiles; + this.fastExistCheckEx(ctx, path, callback, function () { + var next = function (base) { + if (!_this._readDir) + return callback(null, base); + _this._readDir(path, { + context: ctx + }, function (e, paths) { + if (e) + return callback(e); + if (paths.length === 0) + return callback(null, base); + if (paths[0].constructor === String) + base = base.concat(paths); + else + base = base.concat(paths.map(function (p) { return p.fileName(); })); + callback(null, base); + }); + }; + if (!retrieveExternalFiles) + return next([]); + _this.getFullPath(ctx, function (e, thisFullPath) { + if (e) + return callback(e); + ctx.server.getChildFileSystems(thisFullPath.getChildPath(path), function (fss) { + next(fss.map(function (f) { return f.path.fileName(); })); + }); + }); + }); + }; + FileSystem.prototype.creationDate = function (ctx, path, callback) { + var _this = this; + this.fastExistCheckEx(ctx, path, callback, function () { + if (!_this._creationDate && !_this._lastModifiedDate) + return callback(null, 0); + if (!_this._creationDate) + return _this.lastModifiedDate(ctx, path, callback); + _this._creationDate(path, { + context: ctx + }, callback); + }); + }; + FileSystem.prototype.lastModifiedDate = function (ctx, path, callback) { + var _this = this; + this.fastExistCheckEx(ctx, path, callback, function () { + if (!_this._creationDate && !_this._lastModifiedDate) + return callback(null, 0); + if (!_this._lastModifiedDate) + return _this.creationDate(ctx, path, callback); + _this._lastModifiedDate(path, { + context: ctx + }, callback); + }); + }; + FileSystem.prototype.webName = function (ctx, path, callback) { + var _this = this; + this.fastExistCheckEx(ctx, path, callback, function () { + if (path.isRoot()) + _this.getFullPath(ctx, function (e, path) { return callback(e, e ? null : path.fileName()); }); + else + callback(null, path.fileName()); + }); + }; + FileSystem.prototype.displayName = function (ctx, path, callback) { + var _this = this; + this.fastExistCheckEx(ctx, path, callback, function () { + if (!_this._displayName) + return _this.webName(ctx, path, callback); + _this._displayName(path, { + context: ctx + }, callback); + }); + }; + FileSystem.prototype.type = function (ctx, path, callback) { + var _this = this; + this.fastExistCheckEx(ctx, path, callback, function () { + _this._type(path, { + context: ctx + }, callback); + }); + }; + FileSystem.prototype.addSubTree = function (ctx, _rootPath, _tree, _callback) { + var _this = this; + var callback = _callback ? _callback : _tree; + var tree = _callback ? _tree : _rootPath; + var rootPath = _callback ? _rootPath : new Path_1.Path('/'); + if (tree.constructor === CommonTypes_1.ResourceType) { + this.create(ctx, rootPath, tree, callback); + } + else { + new Workflow_1.Workflow() + .each(Object.keys(tree), function (name, cb) { + var value = tree[name]; + if (value.constructor === CommonTypes_1.ResourceType) + _this.addSubTree(ctx, rootPath.getChildPath(name), value, cb); + else + _this.addSubTree(ctx, rootPath.getChildPath(name), CommonTypes_1.ResourceType.Directory, function (e) { + if (e) + return cb(e); + _this.addSubTree(ctx, rootPath.getChildPath(name), value, cb); + }); + }) + .error(callback) + .done(function () { return callback(); }); + } + }; + FileSystem.prototype.listDeepLocks = function (ctx, startPath, _depth, _callback) { + var _this = this; + var depth = _callback ? _depth : 0; + var callback = _callback ? _callback : _depth; + this.lockManager(ctx, startPath, function (e, lm) { + if (e) + return callback(e); + lm.getLocks(function (e, locks) { + if (e) + return callback(e); + if (depth != -1) + locks = locks.filter(function (f) { return f.depth === -1 || f.depth >= depth; }); + var go = function (fs, parentPath) { + var destDepth = depth === -1 ? -1 : depth + 1; + fs.listDeepLocks(ctx, parentPath, destDepth, function (e, pLocks) { + if (e) + return callback(e); + if (locks && locks.length > 0) + pLocks[startPath.toString()] = locks; + callback(null, pLocks); + }); + }; + if (!startPath.isRoot()) + return go(_this, startPath.getParent()); + _this.getFullPath(ctx, function (e, fsPath) { + if (e) + return callback(e); + if (fsPath.isRoot()) { + var result = {}; + if (locks && locks.length > 0) + result[startPath.toString()] = locks; + return callback(null, result); + } + ctx.server.getFileSystem(fsPath.getParent(), function (fs, _, subPath) { + go(fs, subPath); + }); + }); + }); + }); + }; + FileSystem.prototype.getFullPath = function (ctx, _path, _callback) { + var path = _callback ? _path : undefined; + var callback = _callback ? _callback : _path; + ctx.server.getFileSystemPath(this, function (fsPath) { + callback(null, path ? fsPath.getChildPath(path) : fsPath); + }); + }; + FileSystem.prototype.checkPrivilege = function (ctx, path, privileges, callback) { + if (privileges.constructor === String) + privileges = [privileges]; + var resource = this.resource(ctx, path); + new Workflow_1.Workflow() + .each(privileges, function (privilege, cb) { + if (!privilege) + return cb(null, true); + var method = ctx.server.options.privilegeManager[privilege]; + if (!method) + return cb(null, true); + method(ctx, resource, cb); + }) + .error(function (e) { return callback(e, false); }) + .done(function (successes) { return callback(null, successes.every(function (s) { return !!s; })); }); + }; + FileSystem.prototype.serialize = function (callback) { + this.serializer().serialize(this, callback); + }; + return FileSystem; +}()); +exports.FileSystem = FileSystem; diff --git a/lib/manager/v2/fileSystem/LockManager.d.ts b/lib/manager/v2/fileSystem/LockManager.d.ts new file mode 100644 index 00000000..860f60ab --- /dev/null +++ b/lib/manager/v2/fileSystem/LockManager.d.ts @@ -0,0 +1,17 @@ +import { Lock } from '../../../resource/lock/Lock'; +import { ReturnCallback, SimpleCallback } from './CommonTypes'; +export interface ILockManager { + getLocks(callback: ReturnCallback): void; + setLock(lock: Lock, callback: SimpleCallback): void; + removeLock(uuid: string, callback: ReturnCallback): void; + getLock(uuid: string, callback: ReturnCallback): void; + refresh(uuid: string, timeout: number, callback: ReturnCallback): void; +} +export declare class LocalLockManager implements ILockManager { + locks: Lock[]; + getLocks(callback: ReturnCallback): void; + setLock(lock: Lock, callback: SimpleCallback): void; + removeLock(uuid: string, callback: ReturnCallback): void; + getLock(uuid: string, callback: ReturnCallback): void; + refresh(uuid: string, timeout: number, callback: ReturnCallback): void; +} diff --git a/lib/manager/v2/fileSystem/LockManager.js b/lib/manager/v2/fileSystem/LockManager.js new file mode 100644 index 00000000..847d361a --- /dev/null +++ b/lib/manager/v2/fileSystem/LockManager.js @@ -0,0 +1,42 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var LocalLockManager = (function () { + function LocalLockManager() { + this.locks = []; + } + LocalLockManager.prototype.getLocks = function (callback) { + this.locks = this.locks.filter(function (lock) { return !lock.expired(); }); + callback(null, this.locks); + }; + LocalLockManager.prototype.setLock = function (lock, callback) { + this.locks.push(lock); + callback(null); + }; + LocalLockManager.prototype.removeLock = function (uuid, callback) { + for (var index = 0; index < this.locks.length; ++index) + if (this.locks[index].uuid === uuid) { + this.locks.splice(index, 1); + return callback(null, true); + } + callback(null, false); + }; + LocalLockManager.prototype.getLock = function (uuid, callback) { + this.locks = this.locks.filter(function (lock) { return !lock.expired(); }); + for (var _i = 0, _a = this.locks; _i < _a.length; _i++) { + var lock = _a[_i]; + if (lock.uuid === uuid) + return callback(null, lock); + } + callback(); + }; + LocalLockManager.prototype.refresh = function (uuid, timeout, callback) { + this.getLock(uuid, function (e, lock) { + if (e || !lock) + return callback(e); + lock.refresh(timeout); + callback(null, lock); + }); + }; + return LocalLockManager; +}()); +exports.LocalLockManager = LocalLockManager; diff --git a/lib/manager/v2/fileSystem/PropertyManager.d.ts b/lib/manager/v2/fileSystem/PropertyManager.d.ts new file mode 100644 index 00000000..463c203a --- /dev/null +++ b/lib/manager/v2/fileSystem/PropertyManager.d.ts @@ -0,0 +1,19 @@ +import { ReturnCallback, SimpleCallback, ResourcePropertyValue } from './CommonTypes'; +export interface PropertyBag { + [name: string]: ResourcePropertyValue; +} +export interface IPropertyManager { + setProperty(name: string, value: ResourcePropertyValue, callback: SimpleCallback): void; + getProperty(name: string, callback: ReturnCallback): void; + removeProperty(name: string, callback: SimpleCallback): void; + getProperties(callback: ReturnCallback, byCopy?: boolean): void; +} +export declare class LocalPropertyManager implements IPropertyManager { + properties: { + [name: string]: ResourcePropertyValue; + }; + setProperty(name: string, value: ResourcePropertyValue, callback: SimpleCallback): void; + getProperty(name: string, callback: ReturnCallback): void; + removeProperty(name: string, callback: SimpleCallback): void; + getProperties(callback: ReturnCallback, byCopy?: boolean): void; +} diff --git a/lib/manager/v2/fileSystem/PropertyManager.js b/lib/manager/v2/fileSystem/PropertyManager.js new file mode 100644 index 00000000..a888d253 --- /dev/null +++ b/lib/manager/v2/fileSystem/PropertyManager.js @@ -0,0 +1,26 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Errors_1 = require("../../../Errors"); +var LocalPropertyManager = (function () { + function LocalPropertyManager() { + this.properties = {}; + } + LocalPropertyManager.prototype.setProperty = function (name, value, callback) { + this.properties[name] = value; + callback(null); + }; + LocalPropertyManager.prototype.getProperty = function (name, callback) { + var property = this.properties[name]; + callback(property ? null : Errors_1.Errors.PropertyNotFound, property); + }; + LocalPropertyManager.prototype.removeProperty = function (name, callback) { + delete this.properties[name]; + callback(null); + }; + LocalPropertyManager.prototype.getProperties = function (callback, byCopy) { + if (byCopy === void 0) { byCopy = false; } + callback(null, byCopy ? this.properties : JSON.parse(JSON.stringify(this.properties))); + }; + return LocalPropertyManager; +}()); +exports.LocalPropertyManager = LocalPropertyManager; diff --git a/lib/manager/v2/fileSystem/Resource.d.ts b/lib/manager/v2/fileSystem/Resource.d.ts new file mode 100644 index 00000000..18b7d94d --- /dev/null +++ b/lib/manager/v2/fileSystem/Resource.d.ts @@ -0,0 +1,63 @@ +/// +import { ReturnCallback, SimpleCallback, Return2Callback, OpenWriteStreamMode, SubTree, ResourceType } from './CommonTypes'; +import { FileSystem } from './FileSystem'; +import { Readable, Writable } from 'stream'; +import { RequestContext } from '../../../server/v2/RequestContext'; +import { LockKind } from '../../../resource/lock/LockKind'; +import { Lock } from '../../../resource/lock/Lock'; +import { Path } from '../Path'; +import { IPropertyManager } from './PropertyManager'; +import { ILockManager } from './LockManager'; +export declare class Resource { + path: Path; + fs: FileSystem; + context: RequestContext; + constructor(path: Path, fs: FileSystem, context: RequestContext); + delete(callback: SimpleCallback): void; + delete(depth: number, callback: SimpleCallback): void; + openWriteStream(callback: Return2Callback): void; + openWriteStream(estimatedSize: number, callback: Return2Callback): void; + openWriteStream(targetSource: boolean, callback: Return2Callback): void; + openWriteStream(targetSource: boolean, estimatedSize: number, callback: Return2Callback): void; + openWriteStream(mode: OpenWriteStreamMode, callback: Return2Callback): void; + openWriteStream(mode: OpenWriteStreamMode, estimatedSize: number, callback: Return2Callback): void; + openWriteStream(mode: OpenWriteStreamMode, targetSource: boolean, callback: Return2Callback): void; + openWriteStream(mode: OpenWriteStreamMode, targetSource: boolean, estimatedSize: number, callback: Return2Callback): void; + openReadStream(callback: ReturnCallback): void; + openReadStream(estimatedSize: number, callback: ReturnCallback): void; + openReadStream(targetSource: boolean, callback: ReturnCallback): void; + openReadStream(targetSource: boolean, estimatedSize: number, callback: ReturnCallback): void; + copy(pathTo: Path, callback: ReturnCallback): void; + copy(pathTo: Path, depth: number, callback: ReturnCallback): void; + copy(pathTo: Path, overwrite: boolean, callback: ReturnCallback): void; + copy(pathTo: Path, overwrite: boolean, depth: number, callback: ReturnCallback): void; + mimeType(callback: ReturnCallback): void; + mimeType(targetSource: boolean, callback: ReturnCallback): void; + size(callback: ReturnCallback): void; + size(targetSource: boolean, callback: ReturnCallback): void; + addSubTree(subTree: SubTree, callback: SimpleCallback): any; + addSubTree(resourceType: ResourceType, callback: SimpleCallback): any; + create(type: ResourceType, callback: SimpleCallback): void; + create(type: ResourceType, createIntermediates: boolean, callback: SimpleCallback): void; + etag(callback: ReturnCallback): void; + move(pathTo: Path, callback: ReturnCallback): void; + move(pathTo: Path, overwrite: boolean, callback: ReturnCallback): void; + rename(newName: string, callback: ReturnCallback): void; + rename(newName: string, overwrite: boolean, callback: ReturnCallback): void; + availableLocks(callback: ReturnCallback): void; + lockManager(callback: ReturnCallback): void; + propertyManager(callback: ReturnCallback): void; + readDir(callback: ReturnCallback): void; + readDir(retrieveExternalFiles: boolean, callback: ReturnCallback): void; + creationDate(callback: ReturnCallback): void; + lastModifiedDate(callback: ReturnCallback): void; + webName(callback: ReturnCallback): void; + displayName(callback: ReturnCallback): void; + type(callback: ReturnCallback): void; + listDeepLocks(callback: ReturnCallback<{ + [path: string]: Lock[]; + }>): any; + listDeepLocks(depth: number, callback: ReturnCallback<{ + [path: string]: Lock[]; + }>): any; +} diff --git a/lib/manager/v2/fileSystem/Resource.js b/lib/manager/v2/fileSystem/Resource.js new file mode 100644 index 00000000..1b8d607d --- /dev/null +++ b/lib/manager/v2/fileSystem/Resource.js @@ -0,0 +1,74 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Resource = (function () { + function Resource(path, fs, context) { + this.path = path; + this.fs = fs; + this.context = context; + } + Resource.prototype.delete = function (_depth, _callback) { + this.fs.delete(this.context, this.path, _depth, _callback); + }; + Resource.prototype.openWriteStream = function (_mode, _targetSource, _estimatedSize, _callback) { + this.fs.openWriteStream(this.context, this.path, _mode, _targetSource, _estimatedSize, _callback); + }; + Resource.prototype.openReadStream = function (_targetSource, _estimatedSize, _callback) { + this.fs.openReadStream(this.context, this.path, _targetSource, _estimatedSize, _callback); + }; + Resource.prototype.copy = function (pathTo, _overwrite, _depth, _callback) { + this.fs.copy(this.context, this.path, pathTo, _overwrite, _depth, _callback); + }; + Resource.prototype.mimeType = function (_targetSource, _callback) { + this.fs.mimeType(this.context, this.path, _targetSource, _callback); + }; + Resource.prototype.size = function (_targetSource, _callback) { + this.fs.size(this.context, this.path, _targetSource, _callback); + }; + Resource.prototype.addSubTree = function (tree, callback) { + this.fs.addSubTree(this.context, this.path, tree, callback); + }; + Resource.prototype.create = function (type, _createIntermediates, _callback) { + this.fs.create(this.context, this.path, type, _createIntermediates, _callback); + }; + Resource.prototype.etag = function (callback) { + this.fs.etag(this.context, this.path, callback); + }; + Resource.prototype.move = function (pathTo, _overwrite, _callback) { + this.fs.move(this.context, this.path, pathTo, _overwrite, _callback); + }; + Resource.prototype.rename = function (newName, _overwrite, _callback) { + this.fs.rename(this.context, this.path, newName, _overwrite, _callback); + }; + Resource.prototype.availableLocks = function (callback) { + this.fs.availableLocks(this.context, this.path, callback); + }; + Resource.prototype.lockManager = function (callback) { + this.fs.lockManager(this.context, this.path, callback); + }; + Resource.prototype.propertyManager = function (callback) { + this.fs.propertyManager(this.context, this.path, callback); + }; + Resource.prototype.readDir = function (_retrieveExternalFiles, _callback) { + this.fs.readDir(this.context, this.path, _retrieveExternalFiles, _callback); + }; + Resource.prototype.creationDate = function (callback) { + this.fs.creationDate(this.context, this.path, callback); + }; + Resource.prototype.lastModifiedDate = function (callback) { + this.fs.lastModifiedDate(this.context, this.path, callback); + }; + Resource.prototype.webName = function (callback) { + this.fs.webName(this.context, this.path, callback); + }; + Resource.prototype.displayName = function (callback) { + this.fs.displayName(this.context, this.path, callback); + }; + Resource.prototype.type = function (callback) { + this.fs.type(this.context, this.path, callback); + }; + Resource.prototype.listDeepLocks = function (_depth, _callback) { + this.fs.listDeepLocks(this.context, this.path, _depth, _callback); + }; + return Resource; +}()); +exports.Resource = Resource; diff --git a/lib/manager/v2/fileSystem/Serialization.d.ts b/lib/manager/v2/fileSystem/Serialization.d.ts new file mode 100644 index 00000000..1acf4204 --- /dev/null +++ b/lib/manager/v2/fileSystem/Serialization.d.ts @@ -0,0 +1,22 @@ +import { ReturnCallback } from './CommonTypes'; +import { FileSystem } from './FileSystem'; +export interface ISerializableFileSystem { + serializer(): FileSystemSerializer; + serialize(callback: ReturnCallback): void; +} +export interface FileSystemSerializer { + uid(): string; + serialize(fs: FileSystem, callback: ReturnCallback): void; + unserialize(serializedData: any, callback: ReturnCallback): void; +} +export interface SerializedData { + [path: string]: { + serializer: string; + data: any; + }; +} +export interface UnserializedData { + [path: string]: FileSystem; +} +export declare function serialize(fileSystems: UnserializedData, callback: ReturnCallback): void; +export declare function unserialize(serializedData: SerializedData, serializers: FileSystemSerializer[], callback: ReturnCallback): void; diff --git a/lib/manager/v2/fileSystem/Serialization.js b/lib/manager/v2/fileSystem/Serialization.js new file mode 100644 index 00000000..6b1f41bc --- /dev/null +++ b/lib/manager/v2/fileSystem/Serialization.js @@ -0,0 +1,48 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Workflow_1 = require("../../../helper/Workflow"); +var Errors_1 = require("../../../Errors"); +function serialize(fileSystems, callback) { + var result = {}; + new Workflow_1.Workflow() + .each(Object.keys(fileSystems), function (path, cb) { + var fs = fileSystems[path]; + var serializer = fs.serializer(); + serializer.serialize(fs, function (e, data) { + if (!e) + result[path] = { + serializer: serializer.uid(), + data: data + }; + cb(e); + }); + }) + .error(callback) + .done(function () { return callback(null, result); }); +} +exports.serialize = serialize; +function unserialize(serializedData, serializers, callback) { + var result = {}; + new Workflow_1.Workflow() + .each(Object.keys(serializedData), function (path, cb) { + var sd = serializedData[path]; + var serializer = null; + for (var _i = 0, serializers_1 = serializers; _i < serializers_1.length; _i++) { + var s = serializers_1[_i]; + if (s.uid() === sd.serializer) { + serializer = s; + break; + } + } + if (!serializer) + return cb(Errors_1.Errors.SerializerNotFound); + serializer.unserialize(sd.data, function (e, fs) { + if (!e) + result[path] = fs; + callback(e); + }); + }) + .error(callback) + .done(function () { return callback(null, result); }); +} +exports.unserialize = unserialize; diff --git a/lib/manager/v2/fileSystem/StandardMethods.d.ts b/lib/manager/v2/fileSystem/StandardMethods.d.ts new file mode 100644 index 00000000..9ad97577 --- /dev/null +++ b/lib/manager/v2/fileSystem/StandardMethods.d.ts @@ -0,0 +1,16 @@ +import { RequestContext } from '../../../server/v2/RequestContext'; +import { Path } from '../Path'; +import { ReturnCallback } from './CommonTypes'; +import { FileSystem } from './FileSystem'; +export declare abstract class StandardMethods { + static standardMove(ctx: RequestContext, subPathFrom: Path, fsFrom: FileSystem, subPathTo: Path, fsTo: FileSystem, callback: ReturnCallback): void; + static standardMove(ctx: RequestContext, subPathFrom: Path, fsFrom: FileSystem, subPathTo: Path, fsTo: FileSystem, overwrite: boolean, callback: ReturnCallback): void; + static standardCopy(ctx: RequestContext, subPathFrom: Path, fsFrom: FileSystem, subPathTo: Path, fsTo: FileSystem, callback: ReturnCallback): void; + static standardCopy(ctx: RequestContext, subPathFrom: Path, fsFrom: FileSystem, subPathTo: Path, fsTo: FileSystem, depth: number, callback: ReturnCallback): void; + static standardCopy(ctx: RequestContext, subPathFrom: Path, fsFrom: FileSystem, subPathTo: Path, fsTo: FileSystem, overwrite: boolean, callback: ReturnCallback): void; + static standardCopy(ctx: RequestContext, subPathFrom: Path, fsFrom: FileSystem, subPathTo: Path, fsTo: FileSystem, overwrite: boolean, depth: number, callback: ReturnCallback): void; + static standardMimeType(ctx: RequestContext, fs: FileSystem, path: Path, targetSource: boolean, callback: ReturnCallback): any; + static standardMimeType(ctx: RequestContext, fs: FileSystem, path: Path, targetSource: boolean, useWebName: boolean, callback: ReturnCallback): any; + static standardMimeType(ctx: RequestContext, fs: FileSystem, path: Path, targetSource: boolean, defaultMimeType: string, callback: ReturnCallback): any; + static standardMimeType(ctx: RequestContext, fs: FileSystem, path: Path, targetSource: boolean, defaultMimeType: string, useWebName: boolean, callback: ReturnCallback): any; +} diff --git a/lib/manager/v2/fileSystem/StandardMethods.js b/lib/manager/v2/fileSystem/StandardMethods.js new file mode 100644 index 00000000..1b330fd1 --- /dev/null +++ b/lib/manager/v2/fileSystem/StandardMethods.js @@ -0,0 +1,190 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Workflow_1 = require("../../../helper/Workflow"); +var Errors_1 = require("../../../Errors"); +var mimeTypes = require("mime-types"); +var StandardMethods = (function () { + function StandardMethods() { + } + StandardMethods.standardMove = function (ctx, subPathFrom, fsFrom, subPathTo, fsTo, _overwrite, _callback) { + var callback = _callback ? _callback : _overwrite; + var overwrite = _callback ? _overwrite : false; + var go = function (fullPathFrom) { + StandardMethods.standardCopy(ctx, subPathFrom, fsFrom, subPathTo, fsTo, overwrite, -1, function (e, overwritten) { + if (e) + return callback(e, overwritten); + if (fullPathFrom) { + ctx.server.removeFileSystem(fullPathFrom, function (nb) { + callback(null, overwritten); + }); + return; + } + fsFrom.delete(ctx, subPathFrom, -1, function (e) { return callback(e, overwritten); }); + }); + }; + if (subPathFrom.isRoot()) { + fsFrom.getFullPath(ctx, function (e, fullPathFrom) { + go(fullPathFrom); + }); + } + else + go(); + }; + StandardMethods.standardCopy = function (ctx, subPathFrom, fsFrom, subPathTo, fsTo, _overwrite, _depth, _callback) { + var overwrite = _overwrite.constructor === Boolean ? _overwrite : false; + var depth = _callback ? _depth : !_depth ? -1 : _overwrite.constructor === Number ? _overwrite : -1; + var callback = _callback ? _callback : _depth ? _depth : _overwrite; + if (subPathFrom.isRoot()) { + fsTo.getFullPath(ctx, subPathTo, function (e, fullPathTo) { + if (e) + return callback(e); + var overwritten = false; + ctx.server.getResource(ctx, fullPathTo, function (e, r) { + if (e) + return callback(e); + r.type(function (e, type) { + if (!e) + overwritten = true; + ctx.server.setFileSystem(fullPathTo, fsFrom, function (success) { + callback(null, overwritten); + }); + }); + }); + }); + return; + } + var go = function () { + var copyProperties = function (callback) { + fsFrom.propertyManager(ctx, subPathFrom, function (e, pmFrom) { + if (e) + return callback(e); + fsTo.propertyManager(ctx, subPathTo, function (e, pmTo) { + if (e) + return callback(e); + pmFrom.getProperties(function (e, props) { + if (e) + return callback(e); + new Workflow_1.Workflow() + .each(Object.keys(props), function (name, cb) { return pmTo.setProperty(name, props[name], cb); }) + .error(callback) + .done(function () { return callback(); }); + }); + }); + }); + }; + var reverse1 = function (e) { + fsTo.delete(ctx, subPathTo, function () { return callback(e); }); + }; + var copyContent = function (callback) { + fsFrom.openReadStream(ctx, subPathFrom, function (e, rStream) { + if (e) + return reverse1(e); + fsTo.openWriteStream(ctx, subPathTo, function (e, wStream) { + if (e) + return reverse1(e); + var _callback = function (e) { + _callback = function () { }; + callback(e); + }; + rStream.pipe(wStream); + rStream.on('error', _callback); + wStream.on('error', _callback); + wStream.on('finish', function () { + _callback(null); + }); + }); + }); + }; + var copyChildren = function (callback) { + fsFrom.readDir(ctx, subPathFrom, false, function (e, files) { + if (e) + callback(e); + var subDepth = depth === -1 ? -1 : Math.max(0, depth - 1); + new Workflow_1.Workflow() + .each(files, function (file, cb) { return StandardMethods.standardCopy(ctx, subPathFrom.getChildPath(file), fsFrom, subPathTo.getChildPath(file), fsTo, overwrite, subDepth, function (e) { return cb(e); }); }) + .error(callback) + .done(function () { return callback(); }); + }); + }; + fsFrom.type(ctx, subPathFrom, function (e, type) { + if (e) + return callback(e); + var overwritten = false; + var startCopy = function () { + var fns = [copyProperties]; + if (type.isDirectory && depth !== 0) + fns.push(copyChildren); + if (type.isFile) + fns.push(copyContent); + new Workflow_1.Workflow() + .each(fns, function (fn, cb) { return fn(cb); }) + .error(function (e) { return callback(e, overwritten); }) + .done(function () { return callback(null, overwritten); }); + }; + fsTo.create(ctx, subPathTo, type, function (e) { + if (e === Errors_1.Errors.ResourceAlreadyExists && overwrite) { + fsTo.delete(ctx, subPathTo, -1, function (e) { + if (e) + return callback(e); + overwritten = true; + fsTo.create(ctx, subPathTo, type, function (e) { + if (e) + return callback(e); + startCopy(); + }); + }); + return; + } + else if (e) + return callback(e); + startCopy(); + }); + }); + }; + fsFrom.fastExistCheckEx(ctx, subPathFrom, callback, function () { + if (!overwrite) + fsTo.fastExistCheckExReverse(ctx, subPathTo, callback, go); + else + go(); + }); + }; + StandardMethods.standardMimeType = function (ctx, fs, path, targetSource, _defaultMimeType, _useWebName, _callback) { + var callback; + var useWebName = false; + var defaultMimeType = 'application/octet-stream'; + if (_defaultMimeType.constructor === Function) { + callback = _defaultMimeType; + } + else if (_defaultMimeType.constructor === Boolean) { + callback = _useWebName; + if (_defaultMimeType !== undefined && _defaultMimeType !== null) + useWebName = _defaultMimeType; + } + else { + callback = _callback; + if (_useWebName !== undefined && _useWebName !== null) + useWebName = _useWebName; + if (_defaultMimeType !== undefined && _defaultMimeType !== null) + defaultMimeType = _defaultMimeType; + } + fs.type(ctx, path, function (e, type) { + if (e) + return callback(e, null); + if (type.isFile) { + var fn = useWebName ? fs.webName : fs.displayName; + fn.bind(fs)(ctx, path, function (e, name) { + if (e) + callback(e, null); + else { + var mt = mimeTypes.contentType(name); + callback(null, mt ? mt : defaultMimeType); + } + }); + } + else + callback(Errors_1.Errors.NoMimeTypeForAFolder, null); + }); + }; + return StandardMethods; +}()); +exports.StandardMethods = StandardMethods; diff --git a/lib/manager/v2/fileSystem/export.d.ts b/lib/manager/v2/fileSystem/export.d.ts new file mode 100644 index 00000000..fea24af6 --- /dev/null +++ b/lib/manager/v2/fileSystem/export.d.ts @@ -0,0 +1,9 @@ +export * from './FileSystem'; +export * from './CommonTypes'; +export * from './ContextualFileSystem'; +export * from './LockManager'; +export * from './PropertyManager'; +export * from './Resource'; +export * from './Serialization'; +export * from './StandardMethods'; +export * from './ContextInfo'; diff --git a/lib/manager/v2/fileSystem/export.js b/lib/manager/v2/fileSystem/export.js new file mode 100644 index 00000000..f15bd025 --- /dev/null +++ b/lib/manager/v2/fileSystem/export.js @@ -0,0 +1,13 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./FileSystem")); +__export(require("./CommonTypes")); +__export(require("./ContextualFileSystem")); +__export(require("./LockManager")); +__export(require("./PropertyManager")); +__export(require("./Resource")); +__export(require("./Serialization")); +__export(require("./StandardMethods")); diff --git a/lib/manager/v2/instances/FTPFileSystem.d.ts b/lib/manager/v2/instances/FTPFileSystem.d.ts new file mode 100644 index 00000000..1ea6dd34 --- /dev/null +++ b/lib/manager/v2/instances/FTPFileSystem.d.ts @@ -0,0 +1,41 @@ +/// +/// +import { LocalPropertyManager, LastModifiedDateInfo, FileSystemSerializer, OpenWriteStreamInfo, PropertyManagerInfo, OpenReadStreamInfo, IPropertyManager, LocalLockManager, CreationDateInfo, LockManagerInfo, SimpleCallback, ReturnCallback, ResourceType, ILockManager, ReadDirInfo, CreateInfo, DeleteInfo, FileSystem, SizeInfo, MoveInfo, TypeInfo } from '../fileSystem/export'; +import { Readable, Writable } from 'stream'; +import { Path } from '../Path'; +import * as Client from 'ftp'; +export declare class _FTPFileSystemResource { + props: LocalPropertyManager; + locks: LocalLockManager; + type: ResourceType; + constructor(data?: _FTPFileSystemResource); +} +export declare class FTPSerializer implements FileSystemSerializer { + uid(): string; + serialize(fs: FTPFileSystem, callback: ReturnCallback): void; + unserialize(serializedData: any, callback: ReturnCallback): void; +} +export declare class FTPFileSystem extends FileSystem { + config: Client.Options; + resources: { + [path: string]: _FTPFileSystemResource; + }; + constructor(config: Client.Options); + protected getRealPath(path: Path): { + realPath: string; + resource: _FTPFileSystemResource; + }; + protected connect(callback: (client: Client) => void): void; + protected _create(path: Path, ctx: CreateInfo, _callback: SimpleCallback): void; + protected _delete(path: Path, ctx: DeleteInfo, _callback: SimpleCallback): void; + protected _openWriteStream(path: Path, ctx: OpenWriteStreamInfo, callback: ReturnCallback): void; + protected _openReadStream(path: Path, ctx: OpenReadStreamInfo, callback: ReturnCallback): void; + protected _move(pathFrom: Path, pathTo: Path, ctx: MoveInfo, callback: ReturnCallback): void; + protected _size(path: Path, ctx: SizeInfo, callback: ReturnCallback): void; + protected _lockManager(path: Path, ctx: LockManagerInfo, callback: ReturnCallback): void; + protected _propertyManager(path: Path, ctx: PropertyManagerInfo, callback: ReturnCallback): void; + protected _readDir(path: Path, ctx: ReadDirInfo, callback: ReturnCallback): void; + protected _creationDate(path: Path, ctx: CreationDateInfo, callback: ReturnCallback): void; + protected _lastModifiedDate(path: Path, ctx: LastModifiedDateInfo, callback: ReturnCallback): void; + protected _type(path: Path, ctx: TypeInfo, callback: ReturnCallback): void; +} diff --git a/lib/manager/v2/instances/FTPFileSystem.js b/lib/manager/v2/instances/FTPFileSystem.js new file mode 100644 index 00000000..fa4c8f38 --- /dev/null +++ b/lib/manager/v2/instances/FTPFileSystem.js @@ -0,0 +1,269 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var export_1 = require("../fileSystem/export"); +var Errors_1 = require("../../../Errors"); +var stream_1 = require("stream"); +var Client = require("ftp"); +var _FTPFileSystemResource = (function () { + function _FTPFileSystemResource(data) { + if (!data) { + this.props = new export_1.LocalPropertyManager(); + this.locks = new export_1.LocalLockManager(); + } + else { + var rs = data; + this.props = rs.props; + this.locks = rs.locks; + } + } + return _FTPFileSystemResource; +}()); +exports._FTPFileSystemResource = _FTPFileSystemResource; +var FTPSerializer = (function () { + function FTPSerializer() { + } + FTPSerializer.prototype.uid = function () { + return 'FTPFSSerializer_1.0.0'; + }; + FTPSerializer.prototype.serialize = function (fs, callback) { + callback(null, { + resources: fs.resources, + config: fs.config + }); + }; + FTPSerializer.prototype.unserialize = function (serializedData, callback) { + var fs = new FTPFileSystem(serializedData.config); + fs.resources = serializedData.resources; + callback(null, fs); + }; + return FTPSerializer; +}()); +exports.FTPSerializer = FTPSerializer; +var FTPFileSystem = (function (_super) { + __extends(FTPFileSystem, _super); + function FTPFileSystem(config) { + var _this = _super.call(this, new FTPSerializer()) || this; + _this.config = config; + _this.resources = { + '/': new _FTPFileSystemResource() + }; + return _this; + } + FTPFileSystem.prototype.getRealPath = function (path) { + var sPath = path.toString(); + return { + realPath: sPath, + resource: this.resources[sPath] + }; + }; + FTPFileSystem.prototype.connect = function (callback) { + var client = new Client(); + client.on('ready', function () { return callback(client); }); + client.connect(this.config); + }; + FTPFileSystem.prototype._create = function (path, ctx, _callback) { + var _this = this; + if (path.isRoot()) + return _callback(Errors_1.Errors.InvalidOperation); + var realPath = this.getRealPath(path).realPath; + this.connect(function (c) { + var callback = function (e) { + if (!e) + _this.resources[path.toString()] = new _FTPFileSystemResource(); + else if (e) + e = Errors_1.Errors.ResourceAlreadyExists; + c.end(); + _callback(e); + }; + if (ctx.type.isDirectory) + c.mkdir(realPath, callback); + else { + _this._openWriteStream(path, { + context: ctx.context, + estimatedSize: 0, + mode: null, + targetSource: true + }, function (e, wStream) { + if (e) + return callback(e); + wStream.end(new Buffer(0), callback); + }); + } + }); + }; + FTPFileSystem.prototype._delete = function (path, ctx, _callback) { + var _this = this; + if (path.isRoot()) + return _callback(Errors_1.Errors.InvalidOperation); + var realPath = this.getRealPath(path).realPath; + this.connect(function (c) { + var callback = function (e) { + if (!e) + delete _this.resources[path.toString()]; + c.end(); + _callback(e); + }; + _this.type(ctx.context, path, function (e, type) { + if (e) + return callback(Errors_1.Errors.ResourceNotFound); + if (type.isDirectory) + c.rmdir(realPath, callback); + else + c.delete(realPath, callback); + }); + }); + }; + FTPFileSystem.prototype._openWriteStream = function (path, ctx, callback) { + if (path.isRoot()) + return callback(Errors_1.Errors.InvalidOperation); + var _a = this.getRealPath(path), realPath = _a.realPath, resource = _a.resource; + this.connect(function (c) { + var wStream = new stream_1.Transform({ + transform: function (chunk, encoding, cb) { + cb(null, chunk); + } + }); + c.put(wStream, realPath, function (e) { + c.end(); + }); + callback(null, wStream); + }); + }; + FTPFileSystem.prototype._openReadStream = function (path, ctx, callback) { + if (path.isRoot()) + return callback(Errors_1.Errors.InvalidOperation); + var realPath = this.getRealPath(path).realPath; + this.connect(function (c) { + c.get(realPath, function (e, rStream) { + if (e) + return callback(Errors_1.Errors.ResourceNotFound, null); + var stream = new stream_1.Transform({ + transform: function (chunk, encoding, cb) { + cb(null, chunk); + } + }); + stream.on('error', function () { + c.end(); + }); + stream.on('finish', function () { + c.end(); + }); + rStream.pipe(stream); + callback(null, stream); + }); + }); + }; + FTPFileSystem.prototype._move = function (pathFrom, pathTo, ctx, callback) { + var _this = this; + if (pathFrom.isRoot()) + return callback(Errors_1.Errors.InvalidOperation); + if (pathTo.isRoot()) + return callback(Errors_1.Errors.InvalidOperation); + var realPathFrom = this.getRealPath(pathFrom).realPath; + var realPathTo = this.getRealPath(pathTo).realPath; + this.connect(function (c) { + c.rename(realPathFrom, realPathTo, function (e) { + if (!e) { + _this.resources[realPathTo] = _this.resources[realPathFrom]; + delete _this.resources[realPathFrom]; + c.end(); + callback(null, true); + } + else { + c.lastMod(realPathTo, function (er) { + if (!er) + e = Errors_1.Errors.ResourceAlreadyExists; + else + e = Errors_1.Errors.ResourceNotFound; + c.end(); + callback(e, false); + }); + } + }); + }); + }; + FTPFileSystem.prototype._size = function (path, ctx, callback) { + if (path.isRoot()) + return callback(Errors_1.Errors.InvalidOperation); + var realPath = this.getRealPath(path).realPath; + this.connect(function (c) { + c.size(realPath, function (e, size) { + c.end(); + callback(e ? Errors_1.Errors.ResourceNotFound : null, size); + }); + }); + }; + FTPFileSystem.prototype._lockManager = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (!resource) { + resource = new _FTPFileSystemResource(); + this.resources[path.toString()] = resource; + } + callback(null, resource.locks); + }; + FTPFileSystem.prototype._propertyManager = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (!resource) { + resource = new _FTPFileSystemResource(); + this.resources[path.toString()] = resource; + } + callback(null, resource.props); + }; + FTPFileSystem.prototype._readDir = function (path, ctx, callback) { + var realPath = this.getRealPath(path).realPath; + this.connect(function (c) { + c.list(realPath, function (e, list) { + c.end(); + if (e) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, list.map(function (el) { return el.name; })); + }); + }); + }; + FTPFileSystem.prototype._creationDate = function (path, ctx, callback) { + this._lastModifiedDate(path, { + context: ctx.context + }, callback); + }; + FTPFileSystem.prototype._lastModifiedDate = function (path, ctx, callback) { + if (path.isRoot()) + return callback(null, 0); + var realPath = this.getRealPath(path).realPath; + this.connect(function (c) { + c.lastMod(realPath, function (e, date) { + c.end(); + callback(e ? Errors_1.Errors.ResourceNotFound : null, !date ? 0 : date.valueOf()); + }); + }); + }; + FTPFileSystem.prototype._type = function (path, ctx, callback) { + if (path.isRoot()) + return callback(null, export_1.ResourceType.Directory); + var realPath = this.getRealPath(path.getParent()).realPath; + this.connect(function (c) { + c.list(realPath, function (e, list) { + c.end(); + if (e) + return callback(Errors_1.Errors.ResourceNotFound); + for (var _i = 0, list_1 = list; _i < list_1.length; _i++) { + var element = list_1[_i]; + if (element.name === path.fileName()) + return callback(null, element.type === '-' ? export_1.ResourceType.File : export_1.ResourceType.Directory); + } + callback(Errors_1.Errors.ResourceNotFound); + }); + }); + }; + return FTPFileSystem; +}(export_1.FileSystem)); +exports.FTPFileSystem = FTPFileSystem; diff --git a/lib/manager/v2/instances/PhysicalFileSystem.d.ts b/lib/manager/v2/instances/PhysicalFileSystem.d.ts new file mode 100644 index 00000000..7f627131 --- /dev/null +++ b/lib/manager/v2/instances/PhysicalFileSystem.d.ts @@ -0,0 +1,37 @@ +/// +import { LocalPropertyManager, LastModifiedDateInfo, FileSystemSerializer, OpenWriteStreamInfo, PropertyManagerInfo, OpenReadStreamInfo, IPropertyManager, LocalLockManager, CreationDateInfo, LockManagerInfo, SimpleCallback, ReturnCallback, ResourceType, ILockManager, ReadDirInfo, CreateInfo, DeleteInfo, FileSystem, SizeInfo, MoveInfo, TypeInfo } from '../fileSystem/export'; +import { Readable, Writable } from 'stream'; +import { Path } from '../Path'; +export declare class _PhysicalFileSystemResource { + props: LocalPropertyManager; + locks: LocalLockManager; + constructor(data?: _PhysicalFileSystemResource); +} +export declare class PhysicalSerializer implements FileSystemSerializer { + uid(): string; + serialize(fs: PhysicalFileSystem, callback: ReturnCallback): void; + unserialize(serializedData: any, callback: ReturnCallback): void; +} +export declare class PhysicalFileSystem extends FileSystem { + rootPath: string; + resources: { + [path: string]: _PhysicalFileSystemResource; + }; + constructor(rootPath: string); + protected getRealPath(path: Path): { + realPath: string; + resource: _PhysicalFileSystemResource; + }; + protected _create(path: Path, ctx: CreateInfo, _callback: SimpleCallback): void; + protected _delete(path: Path, ctx: DeleteInfo, _callback: SimpleCallback): void; + protected _openWriteStream(path: Path, ctx: OpenWriteStreamInfo, callback: ReturnCallback): void; + protected _openReadStream(path: Path, ctx: OpenReadStreamInfo, callback: ReturnCallback): void; + protected _move(pathFrom: Path, pathTo: Path, ctx: MoveInfo, callback: ReturnCallback): void; + protected _size(path: Path, ctx: SizeInfo, callback: ReturnCallback): void; + protected _lockManager(path: Path, ctx: LockManagerInfo, callback: ReturnCallback): void; + protected _propertyManager(path: Path, ctx: PropertyManagerInfo, callback: ReturnCallback): void; + protected _readDir(path: Path, ctx: ReadDirInfo, callback: ReturnCallback): void; + protected _creationDate(path: Path, ctx: CreationDateInfo, callback: ReturnCallback): void; + protected _lastModifiedDate(path: Path, ctx: LastModifiedDateInfo, callback: ReturnCallback): void; + protected _type(path: Path, ctx: TypeInfo, callback: ReturnCallback): void; +} diff --git a/lib/manager/v2/instances/PhysicalFileSystem.js b/lib/manager/v2/instances/PhysicalFileSystem.js new file mode 100644 index 00000000..0f4ce834 --- /dev/null +++ b/lib/manager/v2/instances/PhysicalFileSystem.js @@ -0,0 +1,207 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var export_1 = require("../fileSystem/export"); +var path_1 = require("path"); +var Errors_1 = require("../../../Errors"); +var fs = require("fs"); +var _PhysicalFileSystemResource = (function () { + function _PhysicalFileSystemResource(data) { + if (!data) { + this.props = new export_1.LocalPropertyManager(); + this.locks = new export_1.LocalLockManager(); + } + else { + var rs = data; + this.props = rs.props; + this.locks = rs.locks; + } + } + return _PhysicalFileSystemResource; +}()); +exports._PhysicalFileSystemResource = _PhysicalFileSystemResource; +var PhysicalSerializer = (function () { + function PhysicalSerializer() { + } + PhysicalSerializer.prototype.uid = function () { + return 'PhysicalFSSerializer_1.0.0'; + }; + PhysicalSerializer.prototype.serialize = function (fs, callback) { + callback(null, { + resources: fs.resources, + rootPath: fs.rootPath + }); + }; + PhysicalSerializer.prototype.unserialize = function (serializedData, callback) { + var fs = new PhysicalFileSystem(serializedData.rootPath); + fs.resources = serializedData.resources; + callback(null, fs); + }; + return PhysicalSerializer; +}()); +exports.PhysicalSerializer = PhysicalSerializer; +var PhysicalFileSystem = (function (_super) { + __extends(PhysicalFileSystem, _super); + function PhysicalFileSystem(rootPath) { + var _this = _super.call(this, new PhysicalSerializer()) || this; + _this.rootPath = rootPath; + _this.resources = { + '/': new _PhysicalFileSystemResource() + }; + return _this; + } + PhysicalFileSystem.prototype.getRealPath = function (path) { + var sPath = path.toString(); + return { + realPath: path_1.join(this.rootPath, sPath.substr(1)), + resource: this.resources[sPath] + }; + }; + PhysicalFileSystem.prototype._create = function (path, ctx, _callback) { + var _this = this; + var realPath = this.getRealPath(path).realPath; + var callback = function (e) { + if (!e) + _this.resources[path.toString()] = new _PhysicalFileSystemResource(); + else if (e) + e = Errors_1.Errors.ResourceAlreadyExists; + _callback(e); + }; + if (ctx.type.isDirectory) + fs.mkdir(realPath, callback); + else { + if (!fs.constants || !fs.constants.O_CREAT) { + fs.writeFile(realPath, new Buffer(0), callback); + } + else { + fs.open(realPath, fs.constants.O_CREAT, function (e, fd) { + if (e) + return callback(e); + fs.close(fd, callback); + }); + } + } + }; + PhysicalFileSystem.prototype._delete = function (path, ctx, _callback) { + var _this = this; + var realPath = this.getRealPath(path).realPath; + var callback = function (e) { + if (!e) + delete _this.resources[path.toString()]; + _callback(e); + }; + this.type(ctx.context, path, function (e, type) { + if (e) + return callback(Errors_1.Errors.ResourceNotFound); + if (type.isDirectory) + fs.rmdir(realPath, callback); + else + fs.unlink(realPath, callback); + }); + }; + PhysicalFileSystem.prototype._openWriteStream = function (path, ctx, callback) { + var _this = this; + var _a = this.getRealPath(path), realPath = _a.realPath, resource = _a.resource; + fs.open(realPath, 'w+', function (e, fd) { + if (e) + return callback(Errors_1.Errors.ResourceNotFound); + if (!resource) + _this.resources[path.toString()] = new _PhysicalFileSystemResource(); + callback(null, fs.createWriteStream(null, { fd: fd })); + }); + }; + PhysicalFileSystem.prototype._openReadStream = function (path, ctx, callback) { + var realPath = this.getRealPath(path).realPath; + fs.open(realPath, 'r', function (e, fd) { + if (e) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, fs.createReadStream(null, { fd: fd })); + }); + }; + PhysicalFileSystem.prototype._move = function (pathFrom, pathTo, ctx, callback) { + var _this = this; + var realPathFrom = this.getRealPath(pathFrom).realPath; + var realPathTo = this.getRealPath(pathTo).realPath; + fs.rename(realPathFrom, realPathTo, function (e) { + if (!e) { + _this.resources[realPathTo] = _this.resources[realPathFrom]; + delete _this.resources[realPathFrom]; + callback(null, true); + } + else { + fs.stat(realPathTo, function (er) { + if (!er) + e = Errors_1.Errors.ResourceAlreadyExists; + else + e = Errors_1.Errors.ResourceNotFound; + callback(e, false); + }); + } + }); + }; + PhysicalFileSystem.prototype._size = function (path, ctx, callback) { + var realPath = this.getRealPath(path).realPath; + fs.stat(realPath, function (e, stat) { + if (e) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, stat.size); + }); + }; + PhysicalFileSystem.prototype._lockManager = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (!resource) { + resource = new _PhysicalFileSystemResource(); + this.resources[path.toString()] = resource; + } + callback(null, resource.locks); + }; + PhysicalFileSystem.prototype._propertyManager = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (!resource) { + resource = new _PhysicalFileSystemResource(); + this.resources[path.toString()] = resource; + } + callback(null, resource.props); + }; + PhysicalFileSystem.prototype._readDir = function (path, ctx, callback) { + var realPath = this.getRealPath(path).realPath; + fs.readdir(realPath, function (e, files) { + callback(e ? Errors_1.Errors.ResourceNotFound : null, files); + }); + }; + PhysicalFileSystem.prototype._creationDate = function (path, ctx, callback) { + var realPath = this.getRealPath(path).realPath; + fs.stat(realPath, function (e, stat) { + if (e) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, stat.birthtime.valueOf()); + }); + }; + PhysicalFileSystem.prototype._lastModifiedDate = function (path, ctx, callback) { + var realPath = this.getRealPath(path).realPath; + fs.stat(realPath, function (e, stat) { + if (e) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, stat.mtime.valueOf()); + }); + }; + PhysicalFileSystem.prototype._type = function (path, ctx, callback) { + var realPath = this.getRealPath(path).realPath; + fs.stat(realPath, function (e, stat) { + if (e) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, stat.isDirectory() ? export_1.ResourceType.Directory : export_1.ResourceType.File); + }); + }; + return PhysicalFileSystem; +}(export_1.FileSystem)); +exports.PhysicalFileSystem = PhysicalFileSystem; diff --git a/lib/manager/v2/instances/VirtualFileSystem.d.ts b/lib/manager/v2/instances/VirtualFileSystem.d.ts new file mode 100644 index 00000000..f5e19921 --- /dev/null +++ b/lib/manager/v2/instances/VirtualFileSystem.d.ts @@ -0,0 +1,51 @@ +/// +import { LocalPropertyManager, LastModifiedDateInfo, FileSystemSerializer, OpenWriteStreamInfo, PropertyManagerInfo, OpenReadStreamInfo, IPropertyManager, LocalLockManager, CreationDateInfo, LockManagerInfo, SimpleCallback, ReturnCallback, ResourceType, ILockManager, ReadDirInfo, CreateInfo, DeleteInfo, FileSystem, SizeInfo, MoveInfo, TypeInfo } from '../fileSystem/export'; +import { Readable, Writable } from 'stream'; +import { RequestContext } from '../../../server/v2/RequestContext'; +import { Path } from '../Path'; +export declare class _VirtualFileSystemResource { + props: LocalPropertyManager; + locks: LocalLockManager; + content: Buffer[]; + size: number; + lastModifiedDate: number; + creationDate: number; + type: ResourceType; + constructor(data: _VirtualFileSystemResource | ResourceType); + static updateLastModified(r: _VirtualFileSystemResource): void; +} +export declare class VirtualFileReadable extends Readable { + contents: Int8Array[]; + blockIndex: number; + constructor(contents: Int8Array[]); + _read(size: number): void; +} +export declare class VirtualFileWritable extends Writable { + contents: Int8Array[]; + constructor(contents: Int8Array[]); + _write(chunk: Buffer | string | any, encoding: string, callback: (error: Error) => void): void; +} +export declare class VirtualSerializer implements FileSystemSerializer { + uid(): string; + serialize(fs: VirtualFileSystem, callback: ReturnCallback): void; + unserialize(serializedData: any, callback: ReturnCallback): void; +} +export declare class VirtualFileSystem extends FileSystem { + resources: { + [path: string]: _VirtualFileSystemResource; + }; + constructor(serializer?: FileSystemSerializer); + protected _fastExistCheck(ctx: RequestContext, path: Path, callback: (exists: boolean) => void): void; + protected _create(path: Path, ctx: CreateInfo, callback: SimpleCallback): void; + protected _delete(path: Path, ctx: DeleteInfo, callback: SimpleCallback): void; + protected _openWriteStream(path: Path, ctx: OpenWriteStreamInfo, callback: ReturnCallback): void; + protected _openReadStream(path: Path, ctx: OpenReadStreamInfo, callback: ReturnCallback): void; + protected _move(pathFrom: Path, pathTo: Path, ctx: MoveInfo, callback: ReturnCallback): void; + protected _size(path: Path, ctx: SizeInfo, callback: ReturnCallback): void; + protected _lockManager(path: Path, ctx: LockManagerInfo, callback: ReturnCallback): void; + protected _propertyManager(path: Path, ctx: PropertyManagerInfo, callback: ReturnCallback): void; + protected _readDir(path: Path, ctx: ReadDirInfo, callback: ReturnCallback): void; + protected _creationDate(path: Path, ctx: CreationDateInfo, callback: ReturnCallback): void; + protected _lastModifiedDate(path: Path, ctx: LastModifiedDateInfo, callback: ReturnCallback): void; + protected _type(path: Path, ctx: TypeInfo, callback: ReturnCallback): void; +} diff --git a/lib/manager/v2/instances/VirtualFileSystem.js b/lib/manager/v2/instances/VirtualFileSystem.js new file mode 100644 index 00000000..6727987c --- /dev/null +++ b/lib/manager/v2/instances/VirtualFileSystem.js @@ -0,0 +1,200 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var export_1 = require("../fileSystem/export"); +var stream_1 = require("stream"); +var Errors_1 = require("../../../Errors"); +var Path_1 = require("../Path"); +var _VirtualFileSystemResource = (function () { + function _VirtualFileSystemResource(data) { + if (data.constructor === export_1.ResourceType) { + this.lastModifiedDate = Date.now(); + this.creationDate = Date.now(); + this.content = []; + this.props = new export_1.LocalPropertyManager(); + this.locks = new export_1.LocalLockManager(); + this.type = data; + this.size = 0; + } + else { + var rs = data; + this.lastModifiedDate = rs.lastModifiedDate; + this.creationDate = rs.creationDate; + this.content = rs.content; + this.props = rs.props; + this.locks = rs.locks; + this.size = rs.size; + this.type = rs.type; + } + } + _VirtualFileSystemResource.updateLastModified = function (r) { + r.lastModifiedDate = Date.now(); + }; + return _VirtualFileSystemResource; +}()); +exports._VirtualFileSystemResource = _VirtualFileSystemResource; +var VirtualFileReadable = (function (_super) { + __extends(VirtualFileReadable, _super); + function VirtualFileReadable(contents) { + var _this = _super.call(this) || this; + _this.contents = contents; + _this.blockIndex = -1; + return _this; + } + VirtualFileReadable.prototype._read = function (size) { + while (true) { + ++this.blockIndex; + if (this.blockIndex >= this.contents.length) { + this.push(null); + break; + } + if (!this.push(this.contents[this.blockIndex])) + break; + } + }; + return VirtualFileReadable; +}(stream_1.Readable)); +exports.VirtualFileReadable = VirtualFileReadable; +var VirtualFileWritable = (function (_super) { + __extends(VirtualFileWritable, _super); + function VirtualFileWritable(contents) { + var _this = _super.call(this, null) || this; + _this.contents = contents; + return _this; + } + VirtualFileWritable.prototype._write = function (chunk, encoding, callback) { + this.contents.push(chunk); + callback(null); + }; + return VirtualFileWritable; +}(stream_1.Writable)); +exports.VirtualFileWritable = VirtualFileWritable; +var VirtualSerializer = (function () { + function VirtualSerializer() { + } + VirtualSerializer.prototype.uid = function () { + return 'VirtualFSSerializer_1.0.0'; + }; + VirtualSerializer.prototype.serialize = function (fs, callback) { + callback(null, fs.resources); + }; + VirtualSerializer.prototype.unserialize = function (serializedData, callback) { + var fs = new VirtualFileSystem(); + fs.resources = serializedData; + callback(null, fs); + }; + return VirtualSerializer; +}()); +exports.VirtualSerializer = VirtualSerializer; +var VirtualFileSystem = (function (_super) { + __extends(VirtualFileSystem, _super); + function VirtualFileSystem(serializer) { + var _this = _super.call(this, serializer ? serializer : new VirtualSerializer()) || this; + _this.resources = { + '/': new _VirtualFileSystemResource(export_1.ResourceType.Directory) + }; + return _this; + } + VirtualFileSystem.prototype._fastExistCheck = function (ctx, path, callback) { + callback(this.resources[path.toString()] !== undefined); + }; + VirtualFileSystem.prototype._create = function (path, ctx, callback) { + this.resources[path.toString()] = new _VirtualFileSystemResource(ctx.type); + callback(); + }; + VirtualFileSystem.prototype._delete = function (path, ctx, callback) { + delete this.resources[path.toString()]; + callback(); + }; + VirtualFileSystem.prototype._openWriteStream = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (resource === undefined) + return callback(Errors_1.Errors.ResourceNotFound); + var content = []; + var stream = new VirtualFileWritable(content); + stream.on('finish', function () { + resource.content = content; + resource.size = content.map(function (c) { return c.length; }).reduce(function (s, n) { return s + n; }, 0); + _VirtualFileSystemResource.updateLastModified(resource); + }); + callback(null, stream); + }; + VirtualFileSystem.prototype._openReadStream = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (resource === undefined) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, new VirtualFileReadable(resource.content)); + }; + VirtualFileSystem.prototype._move = function (pathFrom, pathTo, ctx, callback) { + var from = pathFrom.toString(); + var to = pathTo.toString(); + var existed = !!this.resources[to]; + var fromExists = !!this.resources[from]; + if (!fromExists) + return callback(Errors_1.Errors.ResourceNotFound); + if (existed && !ctx.overwrite) + return callback(Errors_1.Errors.ResourceAlreadyExists); + this.resources[to] = this.resources[from]; + delete this.resources[from]; + callback(null, existed); + }; + VirtualFileSystem.prototype._size = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (!resource) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, resource.size); + }; + VirtualFileSystem.prototype._lockManager = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (!resource) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, resource.locks); + }; + VirtualFileSystem.prototype._propertyManager = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (!resource) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, resource.props); + }; + VirtualFileSystem.prototype._readDir = function (path, ctx, callback) { + var base = path.toString(true); + var children = []; + for (var subPath in this.resources) { + if (subPath.indexOf(base) === 0) { + var pSubPath = new Path_1.Path(subPath); + if (pSubPath.paths.length === path.paths.length + 1) + children.push(pSubPath); + } + } + callback(null, children); + }; + VirtualFileSystem.prototype._creationDate = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (!resource) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, resource.creationDate); + }; + VirtualFileSystem.prototype._lastModifiedDate = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (!resource) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, resource.lastModifiedDate); + }; + VirtualFileSystem.prototype._type = function (path, ctx, callback) { + var resource = this.resources[path.toString()]; + if (!resource) + return callback(Errors_1.Errors.ResourceNotFound); + callback(null, resource.type); + }; + return VirtualFileSystem; +}(export_1.FileSystem)); +exports.VirtualFileSystem = VirtualFileSystem; diff --git a/lib/resource/export.v2.d.ts b/lib/resource/export.v2.d.ts new file mode 100644 index 00000000..745312bf --- /dev/null +++ b/lib/resource/export.v2.d.ts @@ -0,0 +1,5 @@ +export * from './lock/LockScope'; +export * from './lock/LockKind'; +export * from './lock/LockType'; +export * from './lock/LockBag'; +export * from './lock/Lock'; diff --git a/lib/resource/export.v2.js b/lib/resource/export.v2.js new file mode 100644 index 00000000..dcc56ddc --- /dev/null +++ b/lib/resource/export.v2.js @@ -0,0 +1,11 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +// Locks +__export(require("./lock/LockScope")); +__export(require("./lock/LockKind")); +__export(require("./lock/LockType")); +__export(require("./lock/LockBag")); +__export(require("./lock/Lock")); diff --git a/lib/server/v2/RequestContext.d.ts b/lib/server/v2/RequestContext.d.ts new file mode 100644 index 00000000..028e756d --- /dev/null +++ b/lib/server/v2/RequestContext.d.ts @@ -0,0 +1,67 @@ +/// +import { BasicPrivilege } from '../../user/v2/privilege/IPrivilegeManager'; +import { XMLElement } from '../../helper/XML'; +import { WebDAVServer } from './webDAVServer/WebDAVServer'; +import { FileSystem } from '../../manager/v2/fileSystem/FileSystem'; +import { ReturnCallback } from '../../manager/v2/fileSystem/CommonTypes'; +import { Resource } from '../../manager/v2/fileSystem/Resource'; +import { Path } from '../../manager/v2/Path'; +import { IUser } from '../../user/v2/IUser'; +import * as http from 'http'; +export declare class RequestContextHeaders { + protected request: http.IncomingMessage; + contentLength: number; + isSource: boolean; + depth: number; + host: string; + constructor(request: http.IncomingMessage); + find(name: string, defaultValue?: string): string; + findBestAccept(defaultType?: string): string; +} +export interface RequestedResource { + path: Path; + uri: string; +} +export interface RequestContextExternalOptions { + headers?: { + [name: string]: string; + }; + url?: string; + user?: IUser; +} +export declare class DefaultRequestContextExternalOptions implements RequestContextExternalOptions { + headers: { + [name: string]: string; + }; + url: string; +} +export declare class RequestContext { + server: WebDAVServer; + request: http.IncomingMessage; + response: http.ServerResponse; + exit: () => void; + headers: RequestContextHeaders; + requested: RequestedResource; + user: IUser; + protected constructor(server: WebDAVServer, request: http.IncomingMessage, response: http.ServerResponse, exit: () => void); + static createExternal(server: WebDAVServer): RequestContext; + static createExternal(server: WebDAVServer, callback: (error: Error, ctx: RequestContext) => void): RequestContext; + static createExternal(server: WebDAVServer, options: RequestContextExternalOptions): RequestContext; + static createExternal(server: WebDAVServer, options: RequestContextExternalOptions, callback: (error: Error, ctx: RequestContext) => void): RequestContext; + static create(server: WebDAVServer, request: http.IncomingMessage, response: http.ServerResponse, callback: (error: Error, ctx: RequestContext) => void): void; + noBodyExpected(callback: () => void): void; + checkIfHeader(resource: Resource, callback: () => void): any; + checkIfHeader(fs: FileSystem, path: Path, callback: () => void): any; + requirePrivilegeEx(privileges: BasicPrivilege | BasicPrivilege[], callback: () => void): any; + requirePrivilegeEx(privileges: string | string[], callback: () => void): any; + requirePrivilege(privileges: BasicPrivilege | BasicPrivilege[], callback: (error: Error, can: boolean) => void): any; + requirePrivilege(privileges: string | string[], callback: (error: Error, can: boolean) => void): any; + askForAuthentication(checkForUser: boolean, callback: (error: Error) => void): void; + getResource(callback: ReturnCallback): any; + getResource(path: Path | string, callback: ReturnCallback): any; + fullUri(uri?: string): string; + prefixUri(): string; + writeBody(xmlObject: XMLElement | object): void; + setCode(code: number, message?: string): void; +} +export default RequestContext; diff --git a/lib/server/v2/RequestContext.js b/lib/server/v2/RequestContext.js new file mode 100644 index 00000000..3046a331 --- /dev/null +++ b/lib/server/v2/RequestContext.js @@ -0,0 +1,283 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var XML_1 = require("../../helper/XML"); +var IfParser_1 = require("../../helper/v2/IfParser"); +var HTTPCodes_1 = require("../HTTPCodes"); +var Path_1 = require("../../manager/v2/Path"); +var Errors_1 = require("../../Errors"); +var http = require("http"); +var url = require("url"); +var RequestContextHeaders = (function () { + function RequestContextHeaders(request) { + this.request = request; + this.isSource = this.find('source', 'F').toUpperCase() === 'T' || this.find('translate', 'T').toUpperCase() === 'F'; + this.host = this.find('Host', 'localhost'); + var depth = this.find('Depth'); + try { + if (depth.toLowerCase() === 'infinity') + this.depth = -1; + else + this.depth = Math.max(-1, parseInt(depth, 10)); + } + catch (_) { + this.depth = undefined; + } + try { + this.contentLength = Math.max(0, parseInt(this.find('Content-length', '0'), 10)); + } + catch (_) { + this.contentLength = 0; + } + } + RequestContextHeaders.prototype.find = function (name, defaultValue) { + if (defaultValue === void 0) { defaultValue = null; } + name = name.replace(/(-| )/g, '').toLowerCase(); + for (var k in this.request.headers) + if (k.replace(/(-| )/g, '').toLowerCase() === name) { + var value = this.request.headers[k].trim(); + if (value.length !== 0) + return value; + } + return defaultValue; + }; + RequestContextHeaders.prototype.findBestAccept = function (defaultType) { + if (defaultType === void 0) { defaultType = 'xml'; } + var accepts = this.find('Accept', 'text/xml').split(','); + var regex = { + 'xml': /[^a-z0-9A-Z]xml$/, + 'json': /[^a-z0-9A-Z]json$/ + }; + for (var _i = 0, accepts_1 = accepts; _i < accepts_1.length; _i++) { + var value = accepts_1[_i]; + for (var name_1 in regex) + if (regex[name_1].test(value)) + return name_1; + } + return defaultType; + }; + return RequestContextHeaders; +}()); +exports.RequestContextHeaders = RequestContextHeaders; +var DefaultRequestContextExternalOptions = (function () { + function DefaultRequestContextExternalOptions() { + this.headers = { + host: 'localhost' + }; + this.url = '/'; + } + return DefaultRequestContextExternalOptions; +}()); +exports.DefaultRequestContextExternalOptions = DefaultRequestContextExternalOptions; +var RequestContext = (function () { + function RequestContext(server, request, response, exit) { + this.server = server; + this.request = request; + this.response = response; + this.exit = exit; + this.headers = new RequestContextHeaders(request); + var uri = url.parse(request.url).pathname; + this.requested = { + uri: uri, + path: new Path_1.Path(uri) + }; + } + RequestContext.createExternal = function (server, _options, _callback) { + var defaultValues = new DefaultRequestContextExternalOptions(); + var options = _options && _options.constructor !== Function ? _options : defaultValues; + var callback = _callback ? _callback : _options && _options.constructor === Function ? _options : function () { }; + if (defaultValues !== options) { + for (var name_2 in defaultValues) + if (options[name_2] === undefined) + options[name_2] = defaultValues[name_2]; + } + var ctx = new RequestContext(server, { + headers: options.headers, + url: options.url + }, null, null); + if (!options.user) + server.httpAuthentication.getUser(ctx, function (e, user) { + ctx.user = options.user; + callback(e, ctx); + }); + return ctx; + }; + RequestContext.create = function (server, request, response, callback) { + var ctx = new RequestContext(server, request, response, null); + response.setHeader('DAV', '1,2'); + response.setHeader('Server', server.options.serverName + '/' + server.options.version); + ctx.askForAuthentication(false, function (e) { + if (e) { + callback(e, ctx); + return; + } + server.httpAuthentication.getUser(ctx, function (e, user) { + ctx.user = user; + if (e && e !== Errors_1.Errors.UserNotFound) { + if (server.options.requireAuthentification || e !== Errors_1.Errors.MissingAuthorisationHeader) + return callback(e, ctx); + } + if (server.options.requireAuthentification && (!user || user.isDefaultUser || e === Errors_1.Errors.UserNotFound)) + return callback(Errors_1.Errors.MissingAuthorisationHeader, ctx); + server.getFileSystem(ctx.requested.path, function (fs, _, subPath) { + fs.type(ctx, subPath, function (e, type) { + if (e) + type = undefined; + setAllowHeader(type); + }); + }); + }); + }); + function setAllowHeader(type) { + var allowedMethods = []; + for (var name_3 in server.methods) { + var method = server.methods[name_3]; + if (!method.isValidFor || method.isValidFor(type)) + allowedMethods.push(name_3.toUpperCase()); + } + response.setHeader('Allow', allowedMethods.join(',')); + callback(null, ctx); + } + }; + RequestContext.prototype.noBodyExpected = function (callback) { + if (this.server.options.strictMode && this.headers.contentLength !== 0) { + this.setCode(HTTPCodes_1.HTTPCodes.UnsupportedMediaType); + this.exit(); + } + else + callback(); + }; + RequestContext.prototype.checkIfHeader = function (_fs, _path, _callback) { + var _this = this; + var fs = _callback ? _fs : null; + var path = _callback ? _path : null; + var resource = _callback ? null : _fs; + var callback = _callback ? _callback : _path; + var ifHeader = this.headers.find('If'); + if (!ifHeader) { + callback(); + return; + } + if (!resource) { + resource = fs.resource(this, path); + } + IfParser_1.parseIfHeader(ifHeader)(this, resource, function (e, passed) { + if (e) { + _this.setCode(HTTPCodes_1.HTTPCodes.InternalServerError); + _this.exit(); + } + else if (!passed) { + _this.setCode(HTTPCodes_1.HTTPCodes.PreconditionFailed); + _this.exit(); + } + else + callback(); + }); + }; + RequestContext.prototype.requirePrivilegeEx = function (privileges, callback) { + callback(); + }; + RequestContext.prototype.requirePrivilege = function (privileges, callback) { + //requirePrivilege(privileges, this, resource, callback); + callback(null, true); + }; + RequestContext.prototype.askForAuthentication = function (checkForUser, callback) { + if (checkForUser && this.user !== null && !this.user.isDefaultUser) { + callback(Errors_1.Errors.AlreadyAuthenticated); + return; + } + var auth = this.server.httpAuthentication.askForAuthentication(); + for (var name_4 in auth) + this.response.setHeader(name_4, auth[name_4]); + callback(null); + }; + RequestContext.prototype.getResource = function (_path, _callback) { + var path = _callback ? new Path_1.Path(_path) : this.requested.path; + var callback = _callback ? _callback : _path; + this.server.getResource(this, path, callback); + }; + /* + findHeader(name : string, defaultValue : string = null) : string + { + name = name.replace(/(-| )/g, '').toLowerCase(); + + for(const k in this.request.headers) + if(k.replace(/(-| )/g, '').toLowerCase() === name) + return this.request.headers[k]; + + return defaultValue; + } + + getResource(callback : ReturnCallback) + { + callback(!this.resource ? Errors.ResourceNotFound : null, this.resource); + }*/ + /* + invokeEvent(event : EventsName, subjectResource ?: IResource, details ?: DetailsType) + { + this.server.invoke(event, this, subjectResource, details); + } + wrapEvent(event : EventsName, subjectResource ?: IResource, details ?: DetailsType) + { + const oldExit = this.exit; + this.exit = () => { + if(Math.floor(this.response.statusCode / 100) === 2) + this.invokeEvent(event, subjectResource, details); + + oldExit(); + } + return this.exit; + } + */ + RequestContext.prototype.fullUri = function (uri) { + if (uri === void 0) { uri = null; } + if (!uri) + uri = this.requested.uri; + return (this.prefixUri() + uri).replace(/([^:])\/\//g, '$1/'); + }; + RequestContext.prototype.prefixUri = function () { + return 'http://' + this.headers.host.replace('/', ''); + }; + /* + getResourcePath(resource : IResource, callback : ReturnCallback) + { + if(!resource.parent) + callback(null, '/'); + else + resource.webName((e, name) => process.nextTick(() => { + this.getResourcePath(resource.parent, (e, parentName) => { + callback(e, parentName.replace(/\/$/, '') + '/' + name); + }) + })) + }*/ + RequestContext.prototype.writeBody = function (xmlObject) { + var content = XML_1.XML.toXML(xmlObject); + switch (this.headers.findBestAccept()) { + default: + case 'xml': + this.response.setHeader('Content-Type', 'application/xml; charset="utf-8"'); + this.response.setHeader('Content-Length', content.length.toString()); + this.response.write(content); + break; + case 'json': + content = XML_1.XML.toJSON(content); + this.response.setHeader('Content-Type', 'application/json; charset="utf-8"'); + this.response.setHeader('Content-Length', content.length.toString()); + this.response.write(content); + break; + } + }; + RequestContext.prototype.setCode = function (code, message) { + if (!message) + message = http.STATUS_CODES[code]; + if (!message) { + this.response.statusCode = code; + } + else { + this.response.statusCode = code; + this.response.statusMessage = message; + } + }; + return RequestContext; +}()); +exports.RequestContext = RequestContext; +exports.default = RequestContext; diff --git a/lib/server/v2/WebDAVRequest.d.ts b/lib/server/v2/WebDAVRequest.d.ts new file mode 100644 index 00000000..ebd7cb2a --- /dev/null +++ b/lib/server/v2/WebDAVRequest.d.ts @@ -0,0 +1,11 @@ +/// +import { ResourceType } from '../../manager/v2/fileSystem/CommonTypes'; +import { RequestContext } from './RequestContext'; +import { Readable } from 'stream'; +export { RequestContext } from './RequestContext'; +export { HTTPCodes } from '../HTTPCodes'; +export interface HTTPMethod { + unchunked?(ctx: RequestContext, data: Buffer, callback: () => void): void; + chunked?(ctx: RequestContext, inputStream: Readable, callback: () => void): void; + isValidFor?(type?: ResourceType): boolean; +} diff --git a/lib/server/v2/WebDAVRequest.js b/lib/server/v2/WebDAVRequest.js new file mode 100644 index 00000000..57577e6d --- /dev/null +++ b/lib/server/v2/WebDAVRequest.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var RequestContext_1 = require("./RequestContext"); +exports.RequestContext = RequestContext_1.RequestContext; +var HTTPCodes_1 = require("../HTTPCodes"); +exports.HTTPCodes = HTTPCodes_1.HTTPCodes; diff --git a/lib/server/v2/WebDAVServerOptions.d.ts b/lib/server/v2/WebDAVServerOptions.d.ts new file mode 100644 index 00000000..a1ca6346 --- /dev/null +++ b/lib/server/v2/WebDAVServerOptions.d.ts @@ -0,0 +1,35 @@ +/// +import { HTTPAuthentication } from '../../user/v2/authentication/HTTPAuthentication'; +import { Writable, Readable } from 'stream'; +import { IPrivilegeManager } from '../../user/v2/privilege/IPrivilegeManager'; +import { FileSystem } from '../../manager/v2/fileSystem/FileSystem'; +import { FileSystemSerializer } from '../../manager/v2/fileSystem/Serialization'; +import * as https from 'https'; +export interface IAutoSave { + treeFilePath: string; + tempTreeFilePath: string; + onSaveError?: (error: Error) => void; + streamProvider?: (inputStream: Writable, callback: (outputStream?: Writable) => void) => void; +} +export interface IAutoLoad { + treeFilePath: string; + serializers?: FileSystemSerializer[]; + streamProvider?: (inputStream: Readable, callback: (outputStream?: Readable) => void) => void; +} +export declare class WebDAVServerOptions { + requireAuthentification?: boolean; + httpAuthentication?: HTTPAuthentication; + privilegeManager?: IPrivilegeManager; + rootFileSystem?: FileSystem; + lockTimeout?: number; + strictMode?: boolean; + hostname?: string; + https?: https.ServerOptions; + port?: number; + serverName?: string; + version?: string; + autoSave?: IAutoSave; + autoLoad?: IAutoLoad; +} +export default WebDAVServerOptions; +export declare function setDefaultServerOptions(options: WebDAVServerOptions): WebDAVServerOptions; diff --git a/lib/server/v2/WebDAVServerOptions.js b/lib/server/v2/WebDAVServerOptions.js new file mode 100644 index 00000000..94a160a2 --- /dev/null +++ b/lib/server/v2/WebDAVServerOptions.js @@ -0,0 +1,36 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var HTTPDigestAuthentication_1 = require("../../user/v2/authentication/HTTPDigestAuthentication"); +var FakePrivilegeManager_1 = require("../../user/v2/privilege/FakePrivilegeManager"); +var SimpleUserManager_1 = require("../../user/v2/simple/SimpleUserManager"); +var VirtualFileSystem_1 = require("../../manager/v2/instances/VirtualFileSystem"); +var WebDAVServerOptions = (function () { + function WebDAVServerOptions() { + this.requireAuthentification = false; + this.httpAuthentication = new HTTPDigestAuthentication_1.HTTPDigestAuthentication(new SimpleUserManager_1.SimpleUserManager(), 'default realm'); + this.privilegeManager = new FakePrivilegeManager_1.FakePrivilegeManager(); + this.rootFileSystem = new VirtualFileSystem_1.VirtualFileSystem(); + this.lockTimeout = 3600; + this.strictMode = false; + this.hostname = '::'; + this.https = null; + this.port = 1900; + this.serverName = 'webdav-server'; + this.version = '1.8.0'; + this.autoSave = null; + this.autoLoad = null; + } + return WebDAVServerOptions; +}()); +exports.WebDAVServerOptions = WebDAVServerOptions; +exports.default = WebDAVServerOptions; +function setDefaultServerOptions(options) { + var def = new WebDAVServerOptions(); + if (!options) + return def; + for (var name_1 in def) + if (options[name_1] === undefined) + options[name_1] = def[name_1]; + return options; +} +exports.setDefaultServerOptions = setDefaultServerOptions; diff --git a/lib/server/v2/commands/Commands.d.ts b/lib/server/v2/commands/Commands.d.ts new file mode 100644 index 00000000..c4667311 --- /dev/null +++ b/lib/server/v2/commands/Commands.d.ts @@ -0,0 +1,30 @@ +import NotImplemented from './NotImplemented'; +import Proppatch from './Proppatch'; +import Propfind from './Propfind'; +import Options from './Options'; +import Delete from './Delete'; +import Unlock from './Unlock'; +import Mkcol from './Mkcol'; +import Copy from './Copy'; +import Lock from './Lock'; +import Move from './Move'; +import Head from './Head'; +import Post from './Post'; +import Get from './Get'; +declare const _default: { + NotImplemented: typeof NotImplemented; + Proppatch: typeof Proppatch; + Propfind: typeof Propfind; + Options: typeof Options; + Delete: typeof Delete; + Unlock: typeof Unlock; + Mkcol: typeof Mkcol; + Copy: typeof Copy; + Lock: typeof Lock; + Move: typeof Move; + Head: typeof Head; + Post: typeof Post; + Put: typeof Post; + Get: typeof Get; +}; +export default _default; diff --git a/lib/server/v2/commands/Commands.js b/lib/server/v2/commands/Commands.js new file mode 100644 index 00000000..c03a4c6f --- /dev/null +++ b/lib/server/v2/commands/Commands.js @@ -0,0 +1,32 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var NotImplemented_1 = require("./NotImplemented"); +var Proppatch_1 = require("./Proppatch"); +var Propfind_1 = require("./Propfind"); +var Options_1 = require("./Options"); +var Delete_1 = require("./Delete"); +var Unlock_1 = require("./Unlock"); +var Mkcol_1 = require("./Mkcol"); +var Copy_1 = require("./Copy"); +var Lock_1 = require("./Lock"); +var Move_1 = require("./Move"); +var Head_1 = require("./Head"); +var Post_1 = require("./Post"); +var Put_1 = require("./Put"); +var Get_1 = require("./Get"); +exports.default = { + NotImplemented: NotImplemented_1.default, + Proppatch: Proppatch_1.default, + Propfind: Propfind_1.default, + Options: Options_1.default, + Delete: Delete_1.default, + Unlock: Unlock_1.default, + Mkcol: Mkcol_1.default, + Copy: Copy_1.default, + Lock: Lock_1.default, + Move: Move_1.default, + Head: Head_1.default, + Post: Post_1.default, + Put: Put_1.default, + Get: Get_1.default +}; diff --git a/lib/server/v2/commands/Copy.d.ts b/lib/server/v2/commands/Copy.d.ts new file mode 100644 index 00000000..b7ba7d56 --- /dev/null +++ b/lib/server/v2/commands/Copy.d.ts @@ -0,0 +1,7 @@ +/// +import { HTTPMethod, RequestContext } from '../WebDAVRequest'; +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; + isValidFor(type: ResourceType): boolean; +} diff --git a/lib/server/v2/commands/Copy.js b/lib/server/v2/commands/Copy.js new file mode 100644 index 00000000..3d7588d7 --- /dev/null +++ b/lib/server/v2/commands/Copy.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Move_1 = require("./Move"); +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + Move_1.execute(ctx, 'copy', 'canCopy', callback); + }; + default_1.prototype.isValidFor = function (type) { + return !!type; + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/Delete.d.ts b/lib/server/v2/commands/Delete.d.ts new file mode 100644 index 00000000..3241a4b2 --- /dev/null +++ b/lib/server/v2/commands/Delete.d.ts @@ -0,0 +1,7 @@ +/// +import { RequestContext, HTTPMethod } from '../WebDAVRequest'; +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; + isValidFor(type: ResourceType): boolean; +} diff --git a/lib/server/v2/commands/Delete.js b/lib/server/v2/commands/Delete.js new file mode 100644 index 00000000..5f2c1a1c --- /dev/null +++ b/lib/server/v2/commands/Delete.js @@ -0,0 +1,36 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + ctx.noBodyExpected(function () { + ctx.getResource(function (e, r) { + if (e) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.NotFound); + callback(); + return; + } + ctx.checkIfHeader(r, function () { + //ctx.requirePrivilege([ 'canDelete' ], r, () => { + r.delete(function (e) { return process.nextTick(function () { + if (e) + ctx.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + else { + ctx.setCode(WebDAVRequest_1.HTTPCodes.OK); + //ctx.invokeEvent('delete', r); + } + callback(); + }); }); + //}) + }); + }); + }); + }; + default_1.prototype.isValidFor = function (type) { + return !!type; + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/Get.d.ts b/lib/server/v2/commands/Get.d.ts new file mode 100644 index 00000000..3241a4b2 --- /dev/null +++ b/lib/server/v2/commands/Get.d.ts @@ -0,0 +1,7 @@ +/// +import { RequestContext, HTTPMethod } from '../WebDAVRequest'; +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; + isValidFor(type: ResourceType): boolean; +} diff --git a/lib/server/v2/commands/Get.js b/lib/server/v2/commands/Get.js new file mode 100644 index 00000000..a5bcee80 --- /dev/null +++ b/lib/server/v2/commands/Get.js @@ -0,0 +1,119 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var Errors_1 = require("../../../Errors"); +var stream_1 = require("stream"); +var RangedStream = (function (_super) { + __extends(RangedStream, _super); + function RangedStream(min, max) { + var _this = _super.call(this) || this; + _this.min = min; + _this.max = max; + _this.nb = 0; + return _this; + } + RangedStream.prototype._transform = function (chunk, encoding, callback) { + if (this.nb < this.min) { + this.nb += chunk.length; + if (this.nb > this.min) { + chunk = chunk.slice(this.nb - this.min); + callback(null, chunk); + } + else + callback(null, new Buffer(0)); + } + else if (this.nb > this.max) { + this.nb += chunk.length; + callback(null, new Buffer(0)); + } + else { + this.nb += chunk.length; + if (this.nb > this.max) + chunk = chunk.slice(0, this.max - (this.nb - chunk.length)); + callback(null, chunk); + } + }; + return RangedStream; +}(stream_1.Transform)); +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + ctx.noBodyExpected(function () { + ctx.getResource(function (e, r) { + ctx.checkIfHeader(r, function () { + var targetSource = ctx.headers.isSource; + //ctx.requirePrivilegeEx(targetSource ? [ 'canRead', 'canSource', 'canGetMimeType' ] : [ 'canRead', 'canGetMimeType' ], () => { + r.type(function (e, type) { + if (e) { + ctx.setCode(e === Errors_1.Errors.ResourceNotFound ? WebDAVRequest_1.HTTPCodes.NotFound : WebDAVRequest_1.HTTPCodes.InternalServerError); + return callback(); + } + if (!type.isFile) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.MethodNotAllowed); + return callback(); + } + r.mimeType(targetSource, function (e, mimeType) { return process.nextTick(function () { + if (e) { + ctx.setCode(e === Errors_1.Errors.ResourceNotFound ? WebDAVRequest_1.HTTPCodes.NotFound : WebDAVRequest_1.HTTPCodes.InternalServerError); + return callback(); + } + r.openReadStream(targetSource, function (e, rstream) { return process.nextTick(function () { + if (e) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.MethodNotAllowed); + return callback(); + } + //ctx.invokeEvent('read', r); + var range = ctx.headers.find('Range'); + if (range) { + var rex = /([0-9]+)/g; + var min = parseInt(rex.exec(range)[1], 10); + var max = parseInt(rex.exec(range)[1], 10); + ctx.setCode(WebDAVRequest_1.HTTPCodes.PartialContent); + ctx.response.setHeader('Accept-Ranges', 'bytes'); + ctx.response.setHeader('Content-Type', mimeType); + ctx.response.setHeader('Content-Length', (max - min).toString()); + ctx.response.setHeader('Content-Range', 'bytes ' + min + '-' + max + '/*'); + rstream.on('end', callback); + rstream.pipe(new RangedStream(min, max)).pipe(ctx.response); + } + else { + r.size(targetSource, function (e, size) { + if (e) { + ctx.setCode(e === Errors_1.Errors.ResourceNotFound ? WebDAVRequest_1.HTTPCodes.NotFound : WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); + } + else { + ctx.setCode(WebDAVRequest_1.HTTPCodes.OK); + ctx.response.setHeader('Accept-Ranges', 'bytes'); + ctx.response.setHeader('Content-Type', mimeType); + ctx.response.setHeader('Content-Length', size.toString()); + rstream.on('end', callback); + rstream.pipe(ctx.response); + } + }); + } + }); }); + }); }); + }); + //}) + }); + }); + }); + }; + default_1.prototype.isValidFor = function (type) { + return type && type.isFile; + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/Head.d.ts b/lib/server/v2/commands/Head.d.ts new file mode 100644 index 00000000..b7ba7d56 --- /dev/null +++ b/lib/server/v2/commands/Head.d.ts @@ -0,0 +1,7 @@ +/// +import { HTTPMethod, RequestContext } from '../WebDAVRequest'; +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; + isValidFor(type: ResourceType): boolean; +} diff --git a/lib/server/v2/commands/Head.js b/lib/server/v2/commands/Head.js new file mode 100644 index 00000000..1cd74e72 --- /dev/null +++ b/lib/server/v2/commands/Head.js @@ -0,0 +1,51 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var Errors_1 = require("../../../Errors"); +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + ctx.noBodyExpected(function () { + ctx.getResource(function (e, r) { + var targetSource = ctx.headers.isSource; + ctx.checkIfHeader(r, function () { + //ctx.requirePrivilege(targetSource ? [ 'canRead', 'canSource', 'canGetMimeType' ] : [ 'canRead', 'canGetMimeType' ], r, () => { + r.type(function (e, type) { + if (e) { + ctx.setCode(e === Errors_1.Errors.ResourceNotFound ? WebDAVRequest_1.HTTPCodes.NotFound : WebDAVRequest_1.HTTPCodes.InternalServerError); + return callback(); + } + if (!type.isFile) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.MethodNotAllowed); + return callback(); + } + r.mimeType(targetSource, function (e, mimeType) { return process.nextTick(function () { + if (e) { + ctx.setCode(e === Errors_1.Errors.ResourceNotFound ? WebDAVRequest_1.HTTPCodes.NotFound : WebDAVRequest_1.HTTPCodes.InternalServerError); + return callback(); + } + r.size(targetSource, function (e, size) { + if (e) + ctx.setCode(e === Errors_1.Errors.ResourceNotFound ? WebDAVRequest_1.HTTPCodes.NotFound : WebDAVRequest_1.HTTPCodes.InternalServerError); + else { + ctx.setCode(WebDAVRequest_1.HTTPCodes.OK); + ctx.response.setHeader('Accept-Ranges', 'bytes'); + ctx.response.setHeader('Content-Type', mimeType); + ctx.response.setHeader('Content-Length', size.toString()); + } + callback(); + }); + }); }); + }); + //}) + }); + }); + }); + }; + default_1.prototype.isValidFor = function (type) { + return type && type.isFile; + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/Mkcol.d.ts b/lib/server/v2/commands/Mkcol.d.ts new file mode 100644 index 00000000..b7ba7d56 --- /dev/null +++ b/lib/server/v2/commands/Mkcol.d.ts @@ -0,0 +1,7 @@ +/// +import { HTTPMethod, RequestContext } from '../WebDAVRequest'; +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; + isValidFor(type: ResourceType): boolean; +} diff --git a/lib/server/v2/commands/Mkcol.js b/lib/server/v2/commands/Mkcol.js new file mode 100644 index 00000000..ab152b95 --- /dev/null +++ b/lib/server/v2/commands/Mkcol.js @@ -0,0 +1,49 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var CommonTypes_1 = require("../../../manager/v2/fileSystem/CommonTypes"); +var Errors_1 = require("../../../Errors"); +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + ctx.noBodyExpected(function () { + ctx.checkIfHeader(undefined, function () { + ctx.getResource(function (e, r) { + ctx.getResource(r.path.getParent(), function (e, rParent) { + rParent.type(function (e, parentType) { + if (e) { + ctx.setCode(e === Errors_1.Errors.ResourceNotFound ? WebDAVRequest_1.HTTPCodes.Conflict : WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); + return; + } + if (!parentType.isDirectory) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.Forbidden); + callback(); + return; + } + r.create(CommonTypes_1.ResourceType.Directory, function (e) { + if (e) { + if (e === Errors_1.Errors.WrongParentTypeForCreation) + ctx.setCode(WebDAVRequest_1.HTTPCodes.Conflict); + else if (e === Errors_1.Errors.ResourceAlreadyExists) + ctx.setCode(WebDAVRequest_1.HTTPCodes.MethodNotAllowed); + else + ctx.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + } + else + ctx.setCode(WebDAVRequest_1.HTTPCodes.Created); + callback(); + }); + }); + }); + }); + }); + }); + }; + default_1.prototype.isValidFor = function (type) { + return !type; + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/Move.d.ts b/lib/server/v2/commands/Move.d.ts new file mode 100644 index 00000000..1a1ac711 --- /dev/null +++ b/lib/server/v2/commands/Move.d.ts @@ -0,0 +1,8 @@ +/// +import { HTTPMethod, RequestContext } from '../WebDAVRequest'; +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'; +export declare function execute(ctx: RequestContext, methodName: string, privilegeName: string, callback: () => void): void; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; + isValidFor(type: ResourceType): boolean; +} diff --git a/lib/server/v2/commands/Move.js b/lib/server/v2/commands/Move.js new file mode 100644 index 00000000..b5e3ec18 --- /dev/null +++ b/lib/server/v2/commands/Move.js @@ -0,0 +1,74 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var StandardMethods_1 = require("../../../manager/v2/fileSystem/StandardMethods"); +var Path_1 = require("../../../manager/v2/Path"); +var Errors_1 = require("../../../Errors"); +function execute(ctx, methodName, privilegeName, callback) { + ctx.noBodyExpected(function () { + ctx.getResource(function (e, r) { + ctx.checkIfHeader(r, function () { + //ctx.requirePrivilege([ privilegeName ], r, () => { + var overwrite = ctx.headers.find('overwrite') === 'T'; + var destination = ctx.headers.find('destination'); + if (!destination) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.BadRequest); + callback(); + return; + } + var startIndex = destination.indexOf('://'); + if (startIndex !== -1) { + destination = destination.substring(startIndex + '://'.length); + destination = destination.substring(destination.indexOf('/')); // Remove the hostname + port + } + destination = new Path_1.Path(destination); + if (destination.toString() === ctx.requested.path.toString()) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.Forbidden); + return callback(); + } + var cb = function (e, overwritten) { + if (e === Errors_1.Errors.ResourceNotFound) + ctx.setCode(WebDAVRequest_1.HTTPCodes.NotFound); + else if (e === Errors_1.Errors.InsufficientStorage) + ctx.setCode(WebDAVRequest_1.HTTPCodes.InsufficientStorage); + else if (e === Errors_1.Errors.Locked) + ctx.setCode(WebDAVRequest_1.HTTPCodes.Locked); + else if (e === Errors_1.Errors.ResourceAlreadyExists) + ctx.setCode(WebDAVRequest_1.HTTPCodes.Conflict); + else if (e) + ctx.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + else if (overwritten) + ctx.setCode(WebDAVRequest_1.HTTPCodes.NoContent); + else + ctx.setCode(WebDAVRequest_1.HTTPCodes.Created); + callback(); + }; + ctx.server.getFileSystem(destination, function (destFs, destRootPath, destSubPath) { + if (destFs !== r.fs) { + if (methodName === 'move') + StandardMethods_1.StandardMethods.standardMove(ctx, r.path, r.fs, destSubPath, destFs, overwrite, cb); + else + StandardMethods_1.StandardMethods.standardCopy(ctx, r.path, r.fs, destSubPath, destFs, overwrite, cb); + } + else { + r[methodName](destination, overwrite, cb); + } + }); + //}) + }); + }); + }); +} +exports.execute = execute; +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + execute(ctx, 'move', 'canMove', callback); + }; + default_1.prototype.isValidFor = function (type) { + return !!type; + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/NotImplemented.d.ts b/lib/server/v2/commands/NotImplemented.d.ts new file mode 100644 index 00000000..32070ca7 --- /dev/null +++ b/lib/server/v2/commands/NotImplemented.d.ts @@ -0,0 +1,5 @@ +/// +import { RequestContext, HTTPMethod } from '../WebDAVRequest'; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; +} diff --git a/lib/server/v2/commands/NotImplemented.js b/lib/server/v2/commands/NotImplemented.js new file mode 100644 index 00000000..f765b0a4 --- /dev/null +++ b/lib/server/v2/commands/NotImplemented.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.NotImplemented); + callback(); + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/Options.d.ts b/lib/server/v2/commands/Options.d.ts new file mode 100644 index 00000000..32070ca7 --- /dev/null +++ b/lib/server/v2/commands/Options.d.ts @@ -0,0 +1,5 @@ +/// +import { RequestContext, HTTPMethod } from '../WebDAVRequest'; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; +} diff --git a/lib/server/v2/commands/Options.js b/lib/server/v2/commands/Options.js new file mode 100644 index 00000000..c9d6351e --- /dev/null +++ b/lib/server/v2/commands/Options.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + ctx.noBodyExpected(function () { + ctx.setCode(WebDAVRequest_1.HTTPCodes.OK); + callback(); + }); + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/Post.d.ts b/lib/server/v2/commands/Post.d.ts new file mode 100644 index 00000000..f2aefc6d --- /dev/null +++ b/lib/server/v2/commands/Post.d.ts @@ -0,0 +1,2 @@ +import Put from './Put'; +export default Put; diff --git a/lib/server/v2/commands/Post.js b/lib/server/v2/commands/Post.js new file mode 100644 index 00000000..050c1819 --- /dev/null +++ b/lib/server/v2/commands/Post.js @@ -0,0 +1,4 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Put_1 = require("./Put"); +exports.default = Put_1.default; diff --git a/lib/server/v2/commands/Propfind.d.ts b/lib/server/v2/commands/Propfind.d.ts new file mode 100644 index 00000000..3241a4b2 --- /dev/null +++ b/lib/server/v2/commands/Propfind.d.ts @@ -0,0 +1,7 @@ +/// +import { RequestContext, HTTPMethod } from '../WebDAVRequest'; +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; + isValidFor(type: ResourceType): boolean; +} diff --git a/lib/server/v2/commands/Propfind.js b/lib/server/v2/commands/Propfind.js new file mode 100644 index 00000000..d0a69344 --- /dev/null +++ b/lib/server/v2/commands/Propfind.js @@ -0,0 +1,342 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var XML_1 = require("../../../helper/XML"); +var Workflow_1 = require("../../../helper/Workflow"); +var Errors_1 = require("../../../Errors"); +var http = require("http"); +function dateISO8601(ticks) { + // Adding date + var date = new Date(ticks); + var result = date.toISOString().substring(0, '0000-00-00T00:00:00'.length); + // Adding timezone offset + var offset = date.getTimezoneOffset(); + result += offset < 0 ? '-' : '+'; + offset = Math.abs(offset); + var h = Math.ceil(offset / 60).toString(10); + while (h.length < 2) + h = '0' + h; + var m = (offset % 60).toString(10); + while (m.length < 2) + m = '0' + m; + result += h + ':' + m; + return result; +} +function parseRequestBody(ctx, data) { + var allTrue = { + leftElements: [], + mustDisplay: function () { return true; }, + mustDisplayValue: function () { return true; } + }; + var onlyName = { + leftElements: [], + mustDisplay: function () { return true; }, + mustDisplayValue: function () { return false; } + }; + if (ctx.headers.contentLength <= 0) + return allTrue; + try { + var xml = XML_1.XML.parse(data); + var propfind = xml.find('DAV:propfind'); + if (propfind.findIndex('DAV:propname') !== -1) + return onlyName; + if (propfind.findIndex('DAV:allprop') !== -1) + return allTrue; + var prop_1 = propfind.find('DAV:prop'); + var fn = function (name) { + var index = prop_1.findIndex(name); + if (index === -1) + return false; + prop_1.elements.splice(index, 1); + return true; + }; + return { + leftElements: prop_1.elements, + mustDisplay: fn, + mustDisplayValue: function () { return true; } + }; + } + catch (ex) { + return allTrue; + } +} +function propstatStatus(status) { + return 'HTTP/1.1 ' + status + ' ' + http.STATUS_CODES[status]; +} +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + ctx.getResource(function (e, resource) { + var lockDiscoveryCache = {}; + ctx.checkIfHeader(resource, function () { + var targetSource = ctx.headers.isSource; + var multistatus = XML_1.XML.createElement('D:multistatus', { + 'xmlns:D': 'DAV:' + }); + resource.type(function (e, type) { return process.nextTick(function () { + if (e) { + ctx.setCode(e === Errors_1.Errors.ResourceNotFound ? WebDAVRequest_1.HTTPCodes.NotFound : WebDAVRequest_1.HTTPCodes.InternalServerError); + return callback(); + } + if (!type.isDirectory || ctx.headers.depth === 0) { + addXMLInfo(resource, multistatus, function (e) { + if (!e) + done(multistatus); + else { + if (e === Errors_1.Errors.ResourceNotFound) + ctx.setCode(WebDAVRequest_1.HTTPCodes.NotFound); + else if (e === Errors_1.Errors.BadAuthentication) + ctx.setCode(WebDAVRequest_1.HTTPCodes.Unauthorized); + else + ctx.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); + } + }); + return; + } + //ctx.requirePrivilege('canGetChildren', resource, () => { + resource.readDir(true, function (e, children) { return process.nextTick(function () { + function err(e) { + if (e === Errors_1.Errors.ResourceNotFound) + ctx.setCode(WebDAVRequest_1.HTTPCodes.NotFound); + else if (e === Errors_1.Errors.BadAuthentication) + ctx.setCode(WebDAVRequest_1.HTTPCodes.Unauthorized); + else + ctx.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); + } + addXMLInfo(resource, multistatus, function (e) { + if (e) + return err(e); + new Workflow_1.Workflow() + .each(children, function (childName, cb) { + ctx.server.getResource(ctx, ctx.requested.path.getChildPath(childName), function (e, r) { + if (e) + return cb(e); + addXMLInfo(r, multistatus, cb); + }); + }) + .error(err) + .done(function () { + done(multistatus); + }); + }); + }); }); + //}) + }); }); + function addXMLInfo(resource, multistatus, _callback) { + var reqBody = parseRequestBody(ctx, data); + var response = XML_1.XML.createElement('D:response'); + var callback = function (e) { + if (e === Errors_1.Errors.MustIgnore) + e = null; + else if (!e) + multistatus.add(response); + _callback(e); + }; + var propstat = response.ele('D:propstat'); + /*const privileges : BasicPrivilege[] = [ + 'canGetCreationDate', 'canGetAvailableLocks', 'canGetLastModifiedDate', 'canGetMimeType', 'canGetProperties', 'canGetSize', 'canGetType', 'canGetWebName' + ]; + if(targetSource) + privileges.push('canSource'); + ctx.requireErPrivilege(privileges, resource, (e, can) => { + if(e) + { + callback(e); + return; + } + + if(!can) + { + callback(Errors.BadAuthentication); + return; + }*/ + propstat.ele('D:status').add('HTTP/1.1 200 OK'); + var prop = propstat.ele('D:prop'); + var nb = 1; + function nbOut(error) { + if (nb > 0 && error) { + nb = -1000; + return callback(error); + } + --nb; + if (nb === 0) { + if (reqBody.leftElements.length > 0) { + var propstatError = response.ele('D:propstat'); + var prop_2 = propstatError.ele('D:prop'); + propstatError.ele('D:status').add(propstatStatus(WebDAVRequest_1.HTTPCodes.NotFound)); + for (var i = 0; i < reqBody.leftElements.length; ++i) + if (reqBody.leftElements[i]) + prop_2.ele(reqBody.leftElements[i].name); + } + callback(); + } + } + var tags = {}; + function mustDisplayTag(name) { + if (reqBody.mustDisplay('DAV:' + name)) + tags[name] = { + el: prop.ele('D:' + name), + value: reqBody.mustDisplayValue('DAV:' + name) + }; + else + tags[name] = { + value: false + }; + } + mustDisplayTag('getlastmodified'); + mustDisplayTag('lockdiscovery'); + mustDisplayTag('supportedlock'); + mustDisplayTag('creationdate'); + mustDisplayTag('resourcetype'); + mustDisplayTag('displayname'); + mustDisplayTag('getetag'); + function displayValue(values, fn) { + if (values.constructor === String ? tags[values].value : values.some(function (n) { return tags[n].value; })) { + ++nb; + process.nextTick(fn); + } + } + displayValue('creationdate', function () { + resource.creationDate(function (e, ticks) { return process.nextTick(function () { + if (!e) + tags.creationdate.el.add(dateISO8601(ticks)); + nbOut(e); + }); }); + }); + displayValue('lockdiscovery', function () { + resource.listDeepLocks(function (e, locks) { + if (e) + return nbOut(e); + for (var path in locks) { + for (var _i = 0, _a = locks[path]; _i < _a.length; _i++) { + var _lock = _a[_i]; + var lock = _lock; + var activelock = tags.lockdiscovery.el.ele('D:activelock'); + activelock.ele('D:lockscope').ele('D:' + lock.lockKind.scope.value.toLowerCase()); + activelock.ele('D:locktype').ele('D:' + lock.lockKind.type.value.toLowerCase()); + activelock.ele('D:depth').add('Infinity'); + if (lock.owner) + activelock.ele('D:owner').add(lock.owner); + activelock.ele('D:timeout').add('Second-' + (lock.expirationDate - Date.now())); + activelock.ele('D:locktoken').ele('D:href', undefined, true).add(lock.uuid); + activelock.ele('D:lockroot').ele('D:href', undefined, true).add(ctx.fullUri(path).replace(' ', '%20')); + } + } + nbOut(null); + }); + }); + ++nb; + resource.type(function (e, type) { return process.nextTick(function () { + if (e) + return nbOut(e); + resource.fs.getFullPath(ctx, resource.path, function (e, path) { + if (e) + return nbOut(e); + var p = ctx.fullUri(path.toString()).replace(' ', '%20'); + var href = p.lastIndexOf('/') !== p.length - 1 && type.isDirectory ? p + '/' : p; + response.ele('D:href', undefined, true).add(href); + response.ele('D:location').ele('D:href', undefined, true).add(p); + if (tags.resourcetype.value && type.isDirectory) + tags.resourcetype.el.ele('D:collection'); + if (type.isFile) { + mustDisplayTag('getcontentlength'); + mustDisplayTag('getcontenttype'); + if (tags.getcontenttype.value) { + ++nb; + resource.mimeType(targetSource, function (e, mimeType) { return process.nextTick(function () { + if (!e) + tags.getcontenttype.el.add(mimeType); + nbOut(e); + }); }); + } + if (tags.getcontentlength.value) { + ++nb; + resource.size(targetSource, function (e, size) { return process.nextTick(function () { + if (!e) + tags.getcontentlength.el.add(size === undefined || size === null || size.constructor !== Number ? 0 : size); + nbOut(e); + }); }); + } + } + nbOut(); + }); + }); }); + displayValue('displayname', function () { + var methodDisplayName = resource.webName; + if (resource.displayName) + methodDisplayName = resource.displayName; + methodDisplayName.bind(resource)(function (e, name) { return process.nextTick(function () { + if (!e) + tags.displayname.el.add(name ? name : ''); + nbOut(e); + }); }); + }); + displayValue('supportedlock', function () { + resource.availableLocks(function (e, lockKinds) { return process.nextTick(function () { + if (e) { + nbOut(e); + return; + } + lockKinds.forEach(function (lockKind) { + var lockentry = tags.supportedlock.el.ele('D:lockentry'); + var lockscope = lockentry.ele('D:lockscope'); + lockscope.ele('D:' + lockKind.scope.value.toLowerCase()); + var locktype = lockentry.ele('D:locktype'); + locktype.ele('D:' + lockKind.type.value.toLowerCase()); + }); + nbOut(); + }); }); + }); + displayValue('getlastmodified', function () { + resource.lastModifiedDate(function (e, lastModifiedDate) { return process.nextTick(function () { + if (!e && tags.getlastmodified.value) + tags.getlastmodified.el.add(new Date(lastModifiedDate).toUTCString()); + nbOut(e); + }); }); + }); + displayValue('getetag', function () { + resource.etag(function (e, etag) { return process.nextTick(function () { + if (!e && tags.getetag.value) + tags.getetag.el.add(etag); + nbOut(e); + }); }); + }); + ++nb; + process.nextTick(function () { + resource.propertyManager(function (e, pm) { + if (e) + return nbOut(e); + pm.getProperties(function (e, properties) { + if (e) + return nbOut(e); + for (var name_1 in properties) { + if (reqBody.mustDisplay(name_1)) { + var tag = prop.ele(name_1); + if (reqBody.mustDisplayValue(name_1)) + tag.add(properties[name_1]); + } + } + nbOut(); + }); + }); + }); + nbOut(); + //}) + } + function done(multistatus) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.MultiStatus); + ctx.writeBody(multistatus); + callback(); + } + }); + }); + }; + default_1.prototype.isValidFor = function (type) { + return !!type; + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/Proppatch.d.ts b/lib/server/v2/commands/Proppatch.d.ts new file mode 100644 index 00000000..b7ba7d56 --- /dev/null +++ b/lib/server/v2/commands/Proppatch.d.ts @@ -0,0 +1,7 @@ +/// +import { HTTPMethod, RequestContext } from '../WebDAVRequest'; +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; + isValidFor(type: ResourceType): boolean; +} diff --git a/lib/server/v2/commands/Proppatch.js b/lib/server/v2/commands/Proppatch.js new file mode 100644 index 00000000..7fedc2bf --- /dev/null +++ b/lib/server/v2/commands/Proppatch.js @@ -0,0 +1,80 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var http_1 = require("http"); +var Workflow_1 = require("../../../helper/Workflow"); +var XML_1 = require("../../../helper/XML"); +var Errors_1 = require("../../../Errors"); +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + ctx.getResource(function (e, r) { + ctx.checkIfHeader(r, function () { + //ctx.requirePrivilege([ 'canSetProperty', 'canRemoveProperty' ], r, () => { + var multistatus = XML_1.XML.createElement('D:multistatus', { + 'xmlns:D': 'DAV:' + }); + var response = multistatus.ele('D:response'); + response.ele('D:href', undefined, true).add(ctx.fullUri()); + try { + var xml = XML_1.XML.parse(data); + var root_1 = xml.find('DAV:propertyupdate'); + var finalize_1 = function () { + finalize_1 = function () { + ctx.setCode(WebDAVRequest_1.HTTPCodes.MultiStatus); + ctx.writeBody(multistatus); + callback(); + }; + }; + var notify_1 = function (el, error) { + var code = error ? WebDAVRequest_1.HTTPCodes.Conflict : WebDAVRequest_1.HTTPCodes.OK; + var propstat = response.ele('D:propstat'); + propstat.ele('D:prop').ele(el.name); + propstat.ele('D:status').add('HTTP/1.1 ' + code + ' ' + http_1.STATUS_CODES[code]); + }; + var execute_1 = function (name, eventName, fnProp) { + var list = root_1.findMany(name); + if (list.length === 0) { + finalize_1(); + return; + } + list.forEach(function (el) { + var els = el.find('DAV:prop').elements; + new Workflow_1.Workflow(false) + .each(els, fnProp) + .intermediate(function (el, e) { + /*if(!e) + ctx.invokeEvent(eventName, r, el)*/ + notify_1(el, e); + }) + .done(function () { return finalize_1(); }); + }); + }; + r.propertyManager(function (e, pm) { + if (e) { + ctx.setCode(e === Errors_1.Errors.ResourceNotFound ? WebDAVRequest_1.HTTPCodes.NotFound : WebDAVRequest_1.HTTPCodes.InternalServerError); + return callback(); + } + execute_1('DAV:set', 'setProperty', function (el, callback) { + pm.setProperty(el.name, el.elements, callback); + }); + execute_1('DAV:remove', 'removeProperty', function (el, callback) { + pm.removeProperty(el.name, callback); + }); + }); + } + catch (ex) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.BadRequest); + callback(); + } + //}) + }); + }); + }; + default_1.prototype.isValidFor = function (type) { + return !!type; + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/Put.d.ts b/lib/server/v2/commands/Put.d.ts new file mode 100644 index 00000000..06be59f0 --- /dev/null +++ b/lib/server/v2/commands/Put.d.ts @@ -0,0 +1,8 @@ +/// +import { HTTPMethod, RequestContext } from '../WebDAVRequest'; +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'; +import { Readable } from 'stream'; +export default class implements HTTPMethod { + isValidFor(type: ResourceType): boolean; + chunked(ctx: RequestContext, inputStream: Readable, callback: () => void): void; +} diff --git a/lib/server/v2/commands/Put.js b/lib/server/v2/commands/Put.js new file mode 100644 index 00000000..5f2cf00b --- /dev/null +++ b/lib/server/v2/commands/Put.js @@ -0,0 +1,58 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var Errors_1 = require("../../../Errors"); +var default_1 = (function () { + function default_1() { + } + default_1.prototype.isValidFor = function (type) { + return !type || type.isFile; + }; + default_1.prototype.chunked = function (ctx, inputStream, callback) { + var targetSource = ctx.headers.isSource; + ctx.getResource(function (e, r) { + ctx.checkIfHeader(r, function () { + //ctx.requirePrivilege(targetSource ? [ 'canSource', 'canWrite' ] : [ 'canWrite' ], r, () => { + var mode = 'canCreate'; + r.type(function (e, type) { return process.nextTick(function () { + if (e === Errors_1.Errors.ResourceNotFound) { + mode = 'mustCreate'; + } + else if (e) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); + return; + } + else if (!type.isFile) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.MethodNotAllowed); + callback(); + return; + } + r.openWriteStream(mode, targetSource, ctx.headers.contentLength, function (e, wStream, created) { + if (e) { + ctx.setCode(e === Errors_1.Errors.IntermediateResourceMissing || e === Errors_1.Errors.WrongParentTypeForCreation ? WebDAVRequest_1.HTTPCodes.Conflict : WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); + return; + } + inputStream.pipe(wStream); + wStream.on('finish', function (e) { + if (created) + ctx.setCode(WebDAVRequest_1.HTTPCodes.Created); + else + ctx.setCode(WebDAVRequest_1.HTTPCodes.OK); + //ctx.invokeEvent('write', r); + callback(); + }); + wStream.on('error', function (e) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + callback(); + }); + }); + }); }); + //}) + }); + }); + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/commands/Unlock.d.ts b/lib/server/v2/commands/Unlock.d.ts new file mode 100644 index 00000000..b7ba7d56 --- /dev/null +++ b/lib/server/v2/commands/Unlock.d.ts @@ -0,0 +1,7 @@ +/// +import { HTTPMethod, RequestContext } from '../WebDAVRequest'; +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes'; +export default class implements HTTPMethod { + unchunked(ctx: RequestContext, data: Buffer, callback: () => void): void; + isValidFor(type: ResourceType): boolean; +} diff --git a/lib/server/v2/commands/Unlock.js b/lib/server/v2/commands/Unlock.js new file mode 100644 index 00000000..741725ac --- /dev/null +++ b/lib/server/v2/commands/Unlock.js @@ -0,0 +1,74 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var Errors_1 = require("../../../Errors"); +var default_1 = (function () { + function default_1() { + } + default_1.prototype.unchunked = function (ctx, data, callback) { + if (!ctx.user) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.Forbidden); + callback(); + return; + } + ctx.noBodyExpected(function () { + var token = ctx.headers.find('Lock-Token'); + if (!token) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.BadRequest); + callback(); + return; + } + token = token.replace('<', '').replace('>', '').trim(); + ctx.response.setHeader('Lock-Token', '<' + token + '>'); + ctx.getResource(function (e, r) { + ctx.checkIfHeader(r, function () { + /*ctx.requireErPrivilege([ 'canGetLock', 'canRemoveLock' ], r, (e, can) => { + if(e) + { + ctx.setCode(HTTPCodes.InternalServerError); + callback(); + return; + } + + if(!can) + { + ctx.setCode(HTTPCodes.Forbidden); + callback(); + return; + }*/ + r.lockManager(function (e, lm) { + if (e) { + ctx.setCode(e === Errors_1.Errors.ResourceNotFound ? WebDAVRequest_1.HTTPCodes.NotFound : WebDAVRequest_1.HTTPCodes.InternalServerError); + return callback(); + } + lm.getLock(token, function (e, lock) { + if (e || !lock) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.Conflict); + return callback(); + } + if (!!lock.userUid && lock.userUid !== ctx.user.uid) { + ctx.setCode(WebDAVRequest_1.HTTPCodes.Forbidden); + return callback(); + } + lm.removeLock(lock.uuid, function (e, done) { + if (e || !done) + ctx.setCode(WebDAVRequest_1.HTTPCodes.Forbidden); + else { + //ctx.invokeEvent('unlock', r, lock); + ctx.setCode(WebDAVRequest_1.HTTPCodes.NoContent); + } + callback(); + }); + }); + }); + //}) + }); + }); + }); + }; + default_1.prototype.isValidFor = function (type) { + return !!type; + }; + return default_1; +}()); +exports.default = default_1; diff --git a/lib/server/v2/export.d.ts b/lib/server/v2/export.d.ts new file mode 100644 index 00000000..300ba1ba --- /dev/null +++ b/lib/server/v2/export.d.ts @@ -0,0 +1,5 @@ +export * from './WebDAVServerOptions'; +export * from '../HTTPCodes'; +export * from './RequestContext'; +export * from './WebDAVRequest'; +export * from './webDAVServer/WebDAVServer'; diff --git a/lib/server/v2/export.js b/lib/server/v2/export.js new file mode 100644 index 00000000..85cb1183 --- /dev/null +++ b/lib/server/v2/export.js @@ -0,0 +1,10 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./WebDAVServerOptions")); +__export(require("../HTTPCodes")); +__export(require("./RequestContext")); +__export(require("./WebDAVRequest")); +__export(require("./webDAVServer/WebDAVServer")); diff --git a/lib/server/v2/webDAVServer/BeforeAfter.d.ts b/lib/server/v2/webDAVServer/BeforeAfter.d.ts new file mode 100644 index 00000000..0d137885 --- /dev/null +++ b/lib/server/v2/webDAVServer/BeforeAfter.d.ts @@ -0,0 +1,6 @@ +import { RequestContext } from '../WebDAVRequest'; +export interface RequestListener { + (ctx: RequestContext, next: () => void): void; +} +export declare function invokeBeforeRequest(base: RequestContext, callback: any): void; +export declare function invokeAfterRequest(base: RequestContext, callback: any): void; diff --git a/lib/server/v2/webDAVServer/BeforeAfter.js b/lib/server/v2/webDAVServer/BeforeAfter.js new file mode 100644 index 00000000..d0368f86 --- /dev/null +++ b/lib/server/v2/webDAVServer/BeforeAfter.js @@ -0,0 +1,30 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function invokeBARequest(collection, base, callback) { + function callCallback() { + if (callback) + process.nextTick(callback); + } + if (collection.length === 0) { + callCallback(); + return; + } + var nb = collection.length + 1; + function next() { + --nb; + if (nb === 0) { + callCallback(); + } + else + process.nextTick(function () { return collection[collection.length - nb](base, next); }); + } + next(); +} +function invokeBeforeRequest(base, callback) { + invokeBARequest(this.beforeManagers, base, callback); +} +exports.invokeBeforeRequest = invokeBeforeRequest; +function invokeAfterRequest(base, callback) { + invokeBARequest(this.afterManagers, base, callback); +} +exports.invokeAfterRequest = invokeAfterRequest; diff --git a/lib/server/v2/webDAVServer/Events.d.ts b/lib/server/v2/webDAVServer/Events.d.ts new file mode 100644 index 00000000..d89182bd --- /dev/null +++ b/lib/server/v2/webDAVServer/Events.d.ts @@ -0,0 +1,15 @@ +import { RequestContext } from '../WebDAVRequest'; +import { XMLElement } from '../../../helper/XML'; +import { Path } from '../../../manager/v2/Path'; +import { Resource } from '../../../manager/v2/fileSystem/Resource'; +import { Lock } from '../../../resource/lock/Lock'; +export declare type EventsName = 'create' | 'delete' | 'copy' | 'move' | 'lock' | 'refreshLock' | 'unlock' | 'setProperty' | 'removeProperty' | 'write' | 'read' | 'addChild'; +export declare type DetailsType = Resource | Path | Lock | XMLElement; +export declare type Listener = (ctx: RequestContext, subjectResource: Resource, details?: DetailsType) => void; +export declare function invoke(event: EventsName, ctx: RequestContext, subjectResource?: Resource, details?: DetailsType): void; +export declare function register(event: EventsName, listener: Listener): void; +export declare function registerWithName(event: EventsName, name: string, listener: Listener): void; +export declare function clear(event: EventsName): void; +export declare function clearAll(event: EventsName): void; +export declare function remove(event: EventsName, listener: Listener): void; +export declare function removeByName(event: EventsName, name: string): void; diff --git a/lib/server/v2/webDAVServer/Events.js b/lib/server/v2/webDAVServer/Events.js new file mode 100644 index 00000000..660efb89 --- /dev/null +++ b/lib/server/v2/webDAVServer/Events.js @@ -0,0 +1,54 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function getEventBag(_this, event) { + if (!_this.__events) + _this.__events = {}; + if (event && !_this.__events[event]) { + _this.__events[event] = { + all: [], + named: {} + }; + return _this.__events[event]; + } + return event ? _this.__events[event] : _this.__events; +} +function invoke(event, ctx, subjectResource, details) { + var events = getEventBag(this, event); + events.all.forEach(function (e) { return process.nextTick(function () { return e(ctx, subjectResource, details); }); }); +} +exports.invoke = invoke; +function register(event, listener) { + var events = getEventBag(this, event); + events.all.push(listener); +} +exports.register = register; +function registerWithName(event, name, listener) { + var events = getEventBag(this, event); + events.all.push(listener); + events.named[name] = listener; +} +exports.registerWithName = registerWithName; +function clear(event) { + var events = getEventBag(this, event); + events.all = []; + events.named = {}; +} +exports.clear = clear; +function clearAll(event) { + this.__events = {}; +} +exports.clearAll = clearAll; +function remove(event, listener) { + var events = getEventBag(this, event); + events.all.indexOf(listener); +} +exports.remove = remove; +function removeByName(event, name) { + var events = getEventBag(this, event); + var listener = events.named[name]; + if (listener) { + delete events.named[name]; + events.all.splice(events.all.indexOf(listener), 1); + } +} +exports.removeByName = removeByName; diff --git a/lib/server/v2/webDAVServer/Persistence.d.ts b/lib/server/v2/webDAVServer/Persistence.d.ts new file mode 100644 index 00000000..4248a8b8 --- /dev/null +++ b/lib/server/v2/webDAVServer/Persistence.d.ts @@ -0,0 +1,7 @@ +import { SimpleCallback } from '../../../manager/v2/fileSystem/CommonTypes'; +import { FileSystemSerializer, SerializedData } from '../../../manager/v2/fileSystem/Serialization'; +import { IAutoSave } from '../WebDAVServerOptions'; +export declare function load(data: SerializedData, serializers: FileSystemSerializer[], callback: (error: Error) => void): void; +export declare function autoLoad(callback: SimpleCallback): void; +export declare function save(callback: (error: Error, obj: SerializedData) => void): void; +export declare function autoSave(options: IAutoSave): void; diff --git a/lib/server/v2/webDAVServer/Persistence.js b/lib/server/v2/webDAVServer/Persistence.js new file mode 100644 index 00000000..eef0ce93 --- /dev/null +++ b/lib/server/v2/webDAVServer/Persistence.js @@ -0,0 +1,126 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Serialization_1 = require("../../../manager/v2/fileSystem/Serialization"); +var VirtualFileSystem_1 = require("../../../manager/v2/instances/VirtualFileSystem"); +var zlib = require("zlib"); +var fs = require("fs"); +function defaultSerializers() { + return [ + new VirtualFileSystem_1.VirtualSerializer() + ]; +} +function load(data, serializers, callback) { + var _this = this; + serializers = serializers ? serializers : defaultSerializers(); + Serialization_1.unserialize(data, serializers, function (e, udata) { + _this.fileSystems = udata; + }); +} +exports.load = load; +function autoLoad(callback) { + var _this = this; + var options = this.options.autoLoad; + var oStream = fs.createReadStream(options.treeFilePath); + var stream = oStream.pipe(zlib.createGunzip()); + oStream.on('error', callback); + stream.on('error', callback); + var streamProvider = options.streamProvider; + if (!streamProvider) + streamProvider = function (s, cb) { return cb(s); }; + streamProvider(stream, function (s) { + if (!s) + s = stream; + var data = ''; + s.on('data', function (chunk) { + data += chunk.toString(); + }); + s.on('error', callback); + s.on('end', function () { + var obj = JSON.parse(data.toString()); + _this.load(obj, options.serializers, callback); + }); + }); +} +exports.autoLoad = autoLoad; +function save(callback) { + Serialization_1.serialize(this.fileSystems, callback); +} +exports.save = save; +function autoSave(options) { + var _this = this; + if (!options.streamProvider) + options.streamProvider = function (s, cb) { return cb(s); }; + if (!options.onSaveError) + options.onSaveError = function () { }; + var saving = false; + var saveRequested = false; + this.afterRequest(function (ctx, next) { + switch (ctx.request.method.toUpperCase()) { + case 'PROPPATCH': + case 'DELETE': + case 'MKCOL': + case 'MOVE': + case 'COPY': + case 'POST': + case 'PUT': + // Avoid concurrent saving + if (saving) { + saveRequested = true; + next(); + return; + } + var save_1 = function () { + this.save(function (e, data) { + if (e) { + options.onSaveError(e); + next(); + } + else { + var stream_1 = zlib.createGzip(); + options.streamProvider(stream_1, function (outputStream) { + if (!outputStream) + outputStream = stream_1; + outputStream.pipe(fs.createWriteStream(options.tempTreeFilePath)); + stream_1.end(JSON.stringify(data), function (e) { + if (e) { + options.onSaveError(e); + next(); + return; + } + }); + stream_1.on('close', function () { + fs.unlink(options.treeFilePath, function (e) { + if (e && e.code !== 'ENOENT') { + options.onSaveError(e); + next(); + return; + } + fs.rename(options.tempTreeFilePath, options.treeFilePath, function (e) { + if (e) + options.onSaveError(e); + next(); + }); + }); + }); + }); + } + }); + }; + saving = true; + next = function () { + if (saveRequested) { + saveRequested = false; + save_1.bind(_this)(); + } + else + saving = false; + }; + save_1.bind(_this)(); + break; + default: + next(); + break; + } + }); +} +exports.autoSave = autoSave; diff --git a/lib/server/v2/webDAVServer/StartStop.d.ts b/lib/server/v2/webDAVServer/StartStop.d.ts new file mode 100644 index 00000000..a8310024 --- /dev/null +++ b/lib/server/v2/webDAVServer/StartStop.d.ts @@ -0,0 +1,3 @@ +import { WebDAVServerStartCallback } from './WebDAVServer'; +export declare function start(port?: number | WebDAVServerStartCallback, callback?: WebDAVServerStartCallback): void; +export declare function stop(callback: () => void): void; diff --git a/lib/server/v2/webDAVServer/StartStop.js b/lib/server/v2/webDAVServer/StartStop.js new file mode 100644 index 00000000..dcfda668 --- /dev/null +++ b/lib/server/v2/webDAVServer/StartStop.js @@ -0,0 +1,93 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVRequest_1 = require("../WebDAVRequest"); +var Errors_1 = require("../../../Errors"); +var Persistence_1 = require("./Persistence"); +var https = require("https"); +var http = require("http"); +function start(port, callback) { + var _this = this; + var _port = this.options.port; + var _callback; + if (port && port.constructor === Number) { + _port = port; + if (callback) { + if (callback instanceof Function) + _callback = callback; + else + throw Errors_1.Errors.IllegalArguments; + } + } + else if (port && port.constructor === Function) { + _port = this.options.port; + _callback = port; + if (callback) + throw Errors_1.Errors.IllegalArguments; + } + if (!this.server) { + var serverCreator = this.options.https ? function (c) { return https.createServer(_this.options.https, c); } : function (c) { return http.createServer(c); }; + this.server = serverCreator(function (req, res) { + var method = _this.methods[_this.normalizeMethodName(req.method)]; + if (!method) + method = _this.unknownMethod; + WebDAVRequest_1.RequestContext.create(_this, req, res, function (e, base) { + if (e) { + if (e === Errors_1.Errors.AuenticationPropertyMissing || e === Errors_1.Errors.MissingAuthorisationHeader || e === Errors_1.Errors.BadAuthentication || e === Errors_1.Errors.WrongHeaderFormat) + base.setCode(WebDAVRequest_1.HTTPCodes.Unauthorized); + else + base.setCode(WebDAVRequest_1.HTTPCodes.InternalServerError); + res.end(); + return; + } + base.exit = function () { + base.response.end(); + _this.invokeAfterRequest(base, null); + }; + if (!method.chunked) { + var go_1 = function (data) { + _this.invokeBeforeRequest(base, function () { + method.unchunked(base, data, base.exit); + }); + }; + if (base.headers.contentLength <= 0) { + go_1(new Buffer(0)); + } + else { + var data_1 = new Buffer(base.headers.contentLength); + var index_1 = 0; + req.on('data', function (chunk) { + if (chunk.constructor === String) + chunk = new Buffer(chunk); + for (var i = 0; i < chunk.length && index_1 < data_1.length; ++i, ++index_1) + data_1[index_1] = chunk[i]; + if (index_1 >= base.headers.contentLength) + go_1(data_1); + }); + } + } + else { + _this.invokeBeforeRequest(base, function () { + method.chunked(base, req, base.exit); + }); + } + }); + }); + if (this.options.autoSave) + Persistence_1.autoSave.bind(this)(this.options.autoSave); + } + this.server.listen(_port, this.options.hostname, function () { + if (_callback) + _callback(_this.server); + }); +} +exports.start = start; +function stop(callback) { + callback = callback ? callback : function () { }; + if (this.server) { + this.server.close(callback); + this.server = null; + } + else + process.nextTick(callback); +} +exports.stop = stop; diff --git a/lib/server/v2/webDAVServer/Types.d.ts b/lib/server/v2/webDAVServer/Types.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/lib/server/v2/webDAVServer/Types.js b/lib/server/v2/webDAVServer/Types.js new file mode 100644 index 00000000..5c389580 --- /dev/null +++ b/lib/server/v2/webDAVServer/Types.js @@ -0,0 +1,12 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/* +export interface IResourceTreeNode +{ + r ?: IResource + resource ?: IResource + c ?: ResourceTreeNode[] + children ?: ResourceTreeNode[] +} +export type ResourceTreeNode = IResourceTreeNode | IResource | IResourceTreeNode[] | IResource[]; +*/ diff --git a/lib/server/v2/webDAVServer/WebDAVServer.d.ts b/lib/server/v2/webDAVServer/WebDAVServer.d.ts new file mode 100644 index 00000000..171d36f1 --- /dev/null +++ b/lib/server/v2/webDAVServer/WebDAVServer.d.ts @@ -0,0 +1,72 @@ +/// +import { WebDAVServerOptions } from '../WebDAVServerOptions'; +import { RequestContext, HTTPMethod } from '../WebDAVRequest'; +import { HTTPAuthentication } from '../../../user/v2/authentication/HTTPAuthentication'; +import { IPrivilegeManager } from '../../../user/v2/privilege/IPrivilegeManager'; +import { FileSystem } from '../../../manager/v2/fileSystem/FileSystem'; +import { ReturnCallback } from '../../../manager/v2/fileSystem/CommonTypes'; +import { Resource } from '../../../manager/v2/fileSystem/Resource'; +import { Path } from '../../../manager/v2/Path'; +import * as persistence from './Persistence'; +import * as beforeAfter from './BeforeAfter'; +import * as startStop from './StartStop'; +import * as https from 'https'; +import * as http from 'http'; +export declare type WebDAVServerStartCallback = (server?: http.Server) => void; +export declare class WebDAVServer { + httpAuthentication: HTTPAuthentication; + privilegeManager: IPrivilegeManager; + options: WebDAVServerOptions; + methods: { + [methodName: string]: HTTPMethod; + }; + protected beforeManagers: beforeAfter.RequestListener[]; + protected afterManagers: beforeAfter.RequestListener[]; + protected unknownMethod: HTTPMethod; + protected server: http.Server | https.Server; + protected fileSystems: { + [path: string]: FileSystem; + }; + constructor(options?: WebDAVServerOptions); + rootFileSystem(): FileSystem; + getResource(ctx: RequestContext, path: Path | string, callback: ReturnCallback): void; + setFileSystem(path: Path | string, fs: FileSystem, callback: (successed?: boolean) => void): void; + setFileSystem(path: Path | string, fs: FileSystem, override: boolean, callback: (successed?: boolean) => void): void; + setFileSystemSync(path: Path | string, fs: FileSystem, override?: boolean): boolean; + removeFileSystem(path: Path | string, callback: (nbRemoved?: number) => void): void; + removeFileSystem(fs: FileSystem, callback: (nbRemoved?: number) => void): void; + removeFileSystem(fs: FileSystem, checkByReference: boolean, callback: (nbRemoved?: number) => void): void; + removeFileSystemSync(path: Path | string): number; + removeFileSystemSync(fs: FileSystem, checkByReference?: boolean): number; + getFileSystemPath(fs: FileSystem, callback: (path: Path) => void): void; + getFileSystemPath(fs: FileSystem, checkByReference: boolean, callback: (path: Path) => void): void; + getFileSystemPathSync(fs: FileSystem, checkByReference?: boolean): Path; + getChildFileSystems(parentPath: Path, callback: (fss: { + fs: FileSystem; + path: Path; + }[]) => void): void; + getChildFileSystemsSync(parentPath: Path): { + fs: FileSystem; + path: Path; + }[]; + getFileSystem(path: Path, callback: (fs: FileSystem, rootPath: Path, subPath: Path) => void): void; + getFileSystemSync(path: Path): { + fs: FileSystem; + rootPath: Path; + subPath: Path; + }; + onUnknownMethod(unknownMethod: HTTPMethod): void; + start(port: number): any; + start(callback: WebDAVServerStartCallback): any; + start(port: number, callback: WebDAVServerStartCallback): any; + stop: typeof startStop.stop; + autoLoad: typeof persistence.autoLoad; + load: typeof persistence.load; + save: typeof persistence.save; + method(name: string, manager: HTTPMethod): void; + protected normalizeMethodName(method: string): string; + invokeBeforeRequest(base: RequestContext, callback: any): void; + invokeAfterRequest(base: RequestContext, callback: any): void; + beforeRequest(manager: beforeAfter.RequestListener): void; + afterRequest(manager: beforeAfter.RequestListener): void; +} diff --git a/lib/server/v2/webDAVServer/WebDAVServer.js b/lib/server/v2/webDAVServer/WebDAVServer.js new file mode 100644 index 00000000..06b81919 --- /dev/null +++ b/lib/server/v2/webDAVServer/WebDAVServer.js @@ -0,0 +1,195 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var WebDAVServerOptions_1 = require("../WebDAVServerOptions"); +var Commands_1 = require("../commands/Commands"); +var Path_1 = require("../../../manager/v2/Path"); +var persistence = require("./Persistence"); +var beforeAfter = require("./BeforeAfter"); +var startStop = require("./StartStop"); +var WebDAVServer = (function () { + function WebDAVServer(options) { + this.stop = startStop.stop; + // Persistence + this.autoLoad = persistence.autoLoad; + this.load = persistence.load; + this.save = persistence.save; + this.beforeManagers = []; + this.afterManagers = []; + this.methods = {}; + this.options = WebDAVServerOptions_1.setDefaultServerOptions(options); + this.httpAuthentication = this.options.httpAuthentication; + this.privilegeManager = this.options.privilegeManager; + this.fileSystems = { + '/': this.options.rootFileSystem + }; + // Implement all methods in commands/Commands.ts + var commands = Commands_1.default; + for (var k in commands) + if (k === 'NotImplemented') + this.onUnknownMethod(new commands[k]()); + else + this.method(k, new commands[k]()); + } + WebDAVServer.prototype.rootFileSystem = function () { + return this.fileSystems['/']; + }; + WebDAVServer.prototype.getResource = function (ctx, path, callback) { + path = new Path_1.Path(path); + this.getFileSystem(path, function (fs, _, subPath) { + callback(null, fs.resource(ctx, subPath)); + }); + }; + WebDAVServer.prototype.setFileSystem = function (path, fs, _override, _callback) { + var override = _callback ? _override : undefined; + var callback = _callback ? _callback : _override; + var result = this.setFileSystemSync(path, fs, override); + if (callback) + callback(result); + }; + WebDAVServer.prototype.setFileSystemSync = function (path, fs, override) { + if (override === void 0) { override = true; } + var sPath = new Path_1.Path(path).toString(); + if (!override && this.fileSystems[sPath]) + return false; + this.fileSystems[sPath] = fs; + return true; + }; + WebDAVServer.prototype.removeFileSystem = function (fs_path, _checkByReference, _callback) { + var checkByReference = _callback ? _checkByReference : false; + var callback = _callback ? _callback : _checkByReference; + var result = this.removeFileSystemSync(fs_path, checkByReference); + if (callback) + callback(result); + }; + WebDAVServer.prototype.removeFileSystemSync = function (fs_path, checkByReference) { + if (checkByReference === void 0) { checkByReference = false; } + if (fs_path.constructor === Path_1.Path || fs_path.constructor === String) { + var path = new Path_1.Path(fs_path).toString(); + if (this.fileSystems[path] === undefined) + return 0; + else { + delete this.fileSystems[path]; + return 1; + } + } + else { + var fs = fs_path; + var nb = 0; + for (var name_1 in this.fileSystems) + if (checkByReference && this.fileSystems[name_1] === fs || !checkByReference && this.fileSystems[name_1].serializer().uid() === fs.serializer().uid()) { + ++nb; + delete this.fileSystems[name_1]; + } + return nb; + } + }; + WebDAVServer.prototype.getFileSystemPath = function (fs, _checkByReference, _callback) { + var checkByReference = _callback ? _checkByReference : undefined; + var callback = _callback ? _callback : _checkByReference; + callback(this.getFileSystemPathSync(fs, checkByReference)); + }; + WebDAVServer.prototype.getFileSystemPathSync = function (fs, checkByReference) { + checkByReference = checkByReference === null || checkByReference === undefined ? true : checkByReference; + for (var path in this.fileSystems) + if (checkByReference && this.fileSystems[path] === fs || !checkByReference && this.fileSystems[path].serializer().uid() === fs.serializer().uid()) + return new Path_1.Path(path); + return null; + }; + WebDAVServer.prototype.getChildFileSystems = function (parentPath, callback) { + var result = this.getChildFileSystemsSync(parentPath); + callback(result); + }; + WebDAVServer.prototype.getChildFileSystemsSync = function (parentPath) { + var results = []; + var seekPath = parentPath.toString(true); + for (var fsPath in this.fileSystems) { + var pfsPath = new Path_1.Path(fsPath); + if (pfsPath.paths.length === parentPath.paths.length + 1 && fsPath.indexOf(seekPath) === 0) + results.push({ + fs: this.fileSystems[fsPath], + path: pfsPath + }); + } + return results; + }; + WebDAVServer.prototype.getFileSystem = function (path, callback) { + var result = this.getFileSystemSync(path); + callback(result.fs, result.rootPath, result.subPath); + }; + WebDAVServer.prototype.getFileSystemSync = function (path) { + var best = { + index: 0, + rootPath: new Path_1.Path('/') + }; + for (var fsPath in this.fileSystems) { + var pfsPath = new Path_1.Path(fsPath); + if (path.paths.length < pfsPath.paths.length) + continue; + var value = 0; + for (; value < pfsPath.paths.length; ++value) + if (pfsPath.paths[value] !== path.paths[value]) { + value = -1; + break; + } + if (best.index < value) + best = { + index: value, + rootPath: pfsPath + }; + if (value === path.paths.length) + break; // Found the best value possible. + } + var subPath = path.clone(); + for (var _i = 0, _a = best.rootPath.paths; _i < _a.length; _i++) { + var _ = _a[_i]; + subPath.removeRoot(); + } + return { + fs: this.fileSystems[best.rootPath.toString()], + rootPath: best.rootPath, + subPath: subPath + }; + }; + /* + getResourceFromPath(arg : MethodCallArgs, path : FSPath | string[] | string, callback : ReturnCallback) + getResourceFromPath(arg : MethodCallArgs, path : FSPath | string[] | string, rootResource : IResource, callback : ReturnCallback) + getResourceFromPath(arg : MethodCallArgs, path : FSPath | string[] | string, callbackOrRootResource : ReturnCallback | IResource, callback ?: ReturnCallback) + { + resource.getResourceFromPath.bind(this)(arg, path, callbackOrRootResource, callback); + } + + addResourceTree(resoureceTree : ResourceTreeNode, callback : (e : Error) => void) + addResourceTree(rootResource : IResource, resoureceTree : ResourceTreeNode, callback : (e : Error) => void) + addResourceTree(_rootResource : IResource | ResourceTreeNode, _resoureceTree : ResourceTreeNode | (() => void), _callback ?: (e : Error) => void) + { + resource.addResourceTree.bind(this)(_rootResource, _resoureceTree, _callback); + } + */ + WebDAVServer.prototype.onUnknownMethod = function (unknownMethod) { + this.unknownMethod = unknownMethod; + }; + WebDAVServer.prototype.start = function (port, callback) { + startStop.start.bind(this)(port, callback); + }; + WebDAVServer.prototype.method = function (name, manager) { + this.methods[this.normalizeMethodName(name)] = manager; + }; + WebDAVServer.prototype.normalizeMethodName = function (method) { + return method.toLowerCase(); + }; + // Before / After execution + WebDAVServer.prototype.invokeBeforeRequest = function (base, callback) { + beforeAfter.invokeBeforeRequest.bind(this)(base, callback); + }; + WebDAVServer.prototype.invokeAfterRequest = function (base, callback) { + beforeAfter.invokeAfterRequest.bind(this)(base, callback); + }; + WebDAVServer.prototype.beforeRequest = function (manager) { + this.beforeManagers.push(manager); + }; + WebDAVServer.prototype.afterRequest = function (manager) { + this.afterManagers.push(manager); + }; + return WebDAVServer; +}()); +exports.WebDAVServer = WebDAVServer; diff --git a/lib/user/v2/IUser.d.ts b/lib/user/v2/IUser.d.ts new file mode 100644 index 00000000..6b59b7d3 --- /dev/null +++ b/lib/user/v2/IUser.d.ts @@ -0,0 +1,7 @@ +export interface IUser { + uid: string; + isAdministrator: boolean; + isDefaultUser: boolean; + password: string; + username: string; +} diff --git a/lib/user/v2/IUser.js b/lib/user/v2/IUser.js new file mode 100644 index 00000000..c8ad2e54 --- /dev/null +++ b/lib/user/v2/IUser.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/user/v2/IUserManager.d.ts b/lib/user/v2/IUserManager.d.ts new file mode 100644 index 00000000..171013c8 --- /dev/null +++ b/lib/user/v2/IUserManager.d.ts @@ -0,0 +1,4 @@ +import { IUser } from './IUser'; +export interface IUserManager { + getDefaultUser(callback: (user: IUser) => void): any; +} diff --git a/lib/user/v2/IUserManager.js b/lib/user/v2/IUserManager.js new file mode 100644 index 00000000..c8ad2e54 --- /dev/null +++ b/lib/user/v2/IUserManager.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/user/v2/authentication/HTTPAuthentication.d.ts b/lib/user/v2/authentication/HTTPAuthentication.d.ts new file mode 100644 index 00000000..cd855613 --- /dev/null +++ b/lib/user/v2/authentication/HTTPAuthentication.d.ts @@ -0,0 +1,8 @@ +import { RequestContext } from '../../../server/v2/RequestContext'; +import { IUser } from '../IUser'; +export interface HTTPAuthentication { + askForAuthentication(): { + [headeName: string]: string; + }; + getUser(arg: RequestContext, callback: (error: Error, user?: IUser) => void): void; +} diff --git a/lib/user/v2/authentication/HTTPAuthentication.js b/lib/user/v2/authentication/HTTPAuthentication.js new file mode 100644 index 00000000..c8ad2e54 --- /dev/null +++ b/lib/user/v2/authentication/HTTPAuthentication.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/user/v2/authentication/HTTPBasicAuthentication.d.ts b/lib/user/v2/authentication/HTTPBasicAuthentication.d.ts new file mode 100644 index 00000000..7e6592d0 --- /dev/null +++ b/lib/user/v2/authentication/HTTPBasicAuthentication.d.ts @@ -0,0 +1,13 @@ +import { HTTPAuthentication } from './HTTPAuthentication'; +import { RequestContext } from '../../../server/v2/RequestContext'; +import { ITestableUserManager } from '../userManager/ITestableUserManager'; +import { IUser } from '../IUser'; +export declare class HTTPBasicAuthentication implements HTTPAuthentication { + userManager: ITestableUserManager; + realm: string; + constructor(userManager: ITestableUserManager, realm?: string); + askForAuthentication(): { + 'WWW-Authenticate': string; + }; + getUser(arg: RequestContext, callback: (error: Error, user: IUser) => void): void; +} diff --git a/lib/user/v2/authentication/HTTPBasicAuthentication.js b/lib/user/v2/authentication/HTTPBasicAuthentication.js new file mode 100644 index 00000000..2d5fb8f7 --- /dev/null +++ b/lib/user/v2/authentication/HTTPBasicAuthentication.js @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Errors_1 = require("../../../Errors"); +var HTTPBasicAuthentication = (function () { + function HTTPBasicAuthentication(userManager, realm) { + if (realm === void 0) { realm = 'realm'; } + this.userManager = userManager; + this.realm = realm; + } + HTTPBasicAuthentication.prototype.askForAuthentication = function () { + return { + 'WWW-Authenticate': 'Basic realm="' + this.realm + '"' + }; + }; + HTTPBasicAuthentication.prototype.getUser = function (arg, callback) { + var _this = this; + var onError = function (error) { + _this.userManager.getDefaultUser(function (defaultUser) { + callback(error, defaultUser); + }); + }; + var authHeader = arg.headers.find('Authorization'); + if (!authHeader) { + onError(Errors_1.Errors.MissingAuthorisationHeader); + return; + } + if (!/^Basic \s*[a-zA-Z0-9]+=*\s*$/.test(authHeader)) { + onError(Errors_1.Errors.WrongHeaderFormat); + return; + } + var value = Buffer.from(/^Basic \s*([a-zA-Z0-9]+=*)\s*$/.exec(authHeader)[1], 'base64').toString().split(':', 2); + var username = value[0]; + var password = value[1]; + this.userManager.getUserByNamePassword(username, password, function (e, user) { + if (e) + onError(e); // Errors.BadAuthentication + else + callback(null, user); + }); + }; + return HTTPBasicAuthentication; +}()); +exports.HTTPBasicAuthentication = HTTPBasicAuthentication; diff --git a/lib/user/v2/authentication/HTTPDigestAuthentication.d.ts b/lib/user/v2/authentication/HTTPDigestAuthentication.d.ts new file mode 100644 index 00000000..00542d9f --- /dev/null +++ b/lib/user/v2/authentication/HTTPDigestAuthentication.d.ts @@ -0,0 +1,15 @@ +import { HTTPAuthentication } from './HTTPAuthentication'; +import { RequestContext } from '../../../server/v2/RequestContext'; +import { IListUserManager } from '../userManager/IListUserManager'; +import { IUser } from '../IUser'; +export declare class HTTPDigestAuthentication implements HTTPAuthentication { + userManager: IListUserManager; + realm: string; + nonceSize: number; + constructor(userManager: IListUserManager, realm?: string, nonceSize?: number); + generateNonce(): string; + askForAuthentication(): { + 'WWW-Authenticate': string; + }; + getUser(arg: RequestContext, callback: (error: Error, user: IUser) => void): void; +} diff --git a/lib/user/v2/authentication/HTTPDigestAuthentication.js b/lib/user/v2/authentication/HTTPDigestAuthentication.js new file mode 100644 index 00000000..4b0277ea --- /dev/null +++ b/lib/user/v2/authentication/HTTPDigestAuthentication.js @@ -0,0 +1,71 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Errors_1 = require("../../../Errors"); +var crypto = require("crypto"); +function md5(value) { + return crypto.createHash('md5').update(value).digest('hex'); +} +var HTTPDigestAuthentication = (function () { + function HTTPDigestAuthentication(userManager, realm, nonceSize) { + if (realm === void 0) { realm = 'realm'; } + if (nonceSize === void 0) { nonceSize = 50; } + this.userManager = userManager; + this.realm = realm; + this.nonceSize = nonceSize; + } + HTTPDigestAuthentication.prototype.generateNonce = function () { + var buffer = new Buffer(this.nonceSize); + for (var i = 0; i < buffer.length; ++i) + buffer[i] = Math.ceil(Math.random() * 256); + return md5(buffer); + }; + HTTPDigestAuthentication.prototype.askForAuthentication = function () { + return { + 'WWW-Authenticate': 'Digest realm="' + this.realm + '", qop="auth,auth-int", nonce="' + this.generateNonce() + '", opaque="' + this.generateNonce() + '"' + }; + }; + HTTPDigestAuthentication.prototype.getUser = function (arg, callback) { + var _this = this; + var onError = function (error) { + _this.userManager.getDefaultUser(function (defaultUser) { + callback(error, defaultUser); + }); + }; + var authHeader = arg.headers.find('Authorization'); + if (!authHeader) { + onError(Errors_1.Errors.MissingAuthorisationHeader); + return; + } + if (!/^Digest (\s*[a-zA-Z]+\s*=\s*(("(\\"|[^"])+")|([^,\s]+))\s*(,|$))+$/.test(authHeader)) { + onError(Errors_1.Errors.WrongHeaderFormat); + return; + } + authHeader = authHeader.substring(authHeader.indexOf(' ') + 1); // remove the authentication type from the string + var authProps = {}; + var rex = /([a-zA-Z]+)\s*=\s*(?:(?:"((?:\\"|[^"])+)")|([^,\s]+))/g; + var match = rex.exec(authHeader); + while (match) { + authProps[match[1]] = match[3] ? match[3] : match[2]; + match = rex.exec(authHeader); + } + if (!(authProps.username && authProps.nonce && authProps.nc && authProps.cnonce && authProps.qop && authProps.response)) { + onError(Errors_1.Errors.AuenticationPropertyMissing); + return; + } + this.userManager.getUserByName(authProps.username, function (e, user) { + if (e) { + onError(e); + return; + } + var ha1 = md5(authProps.username + ':' + _this.realm + ':' + (user.password ? user.password : '')); + var ha2 = md5(arg.request.method.toString().toUpperCase() + ':' + arg.requested.uri); + var result = md5(ha1 + ':' + authProps.nonce + ':' + authProps.nc + ':' + authProps.cnonce + ':' + authProps.qop + ':' + ha2); + if (result.toLowerCase() === authProps.response.toLowerCase()) + callback(Errors_1.Errors.None, user); + else + onError(Errors_1.Errors.BadAuthentication); + }); + }; + return HTTPDigestAuthentication; +}()); +exports.HTTPDigestAuthentication = HTTPDigestAuthentication; diff --git a/lib/user/v2/export.d.ts b/lib/user/v2/export.d.ts new file mode 100644 index 00000000..cac91675 --- /dev/null +++ b/lib/user/v2/export.d.ts @@ -0,0 +1,9 @@ +export * from './authentication/HTTPDigestAuthentication'; +export * from './authentication/HTTPBasicAuthentication'; +export * from './authentication/HTTPAuthentication'; +export * from './userManager/ITestableUserManager'; +export * from './userManager/IListUserManager'; +export * from './simple/SimpleUserManager'; +export * from './simple/SimpleUser'; +export * from './IUserManager'; +export * from './IUser'; diff --git a/lib/user/v2/export.js b/lib/user/v2/export.js new file mode 100644 index 00000000..da7944a5 --- /dev/null +++ b/lib/user/v2/export.js @@ -0,0 +1,9 @@ +"use strict"; +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +__export(require("./authentication/HTTPDigestAuthentication")); +__export(require("./authentication/HTTPBasicAuthentication")); +__export(require("./simple/SimpleUserManager")); +__export(require("./simple/SimpleUser")); diff --git a/lib/user/v2/privilege/FakePrivilegeManager.d.ts b/lib/user/v2/privilege/FakePrivilegeManager.d.ts new file mode 100644 index 00000000..02ea08c8 --- /dev/null +++ b/lib/user/v2/privilege/FakePrivilegeManager.d.ts @@ -0,0 +1,18 @@ +import { SimplePrivilegeManager } from './SimplePrivilegeManager'; +import { hasNoWriteLock } from './IPrivilegeManager'; +export declare class FakePrivilegeManager extends SimplePrivilegeManager { + constructor(); + canCreate: (arg: any, resource: any, callback: any) => any; + canDelete: typeof hasNoWriteLock; + canWrite: typeof hasNoWriteLock; + canSource: (arg: any, resource: any, callback: any) => any; + canRead: (arg: any, resource: any, callback: any) => any; + canListLocks: (arg: any, resource: any, callback: any) => any; + canSetLock: typeof hasNoWriteLock; + canGetAvailableLocks: (arg: any, resource: any, callback: any) => any; + canAddChild: typeof hasNoWriteLock; + canRemoveChild: typeof hasNoWriteLock; + canGetChildren: (arg: any, resource: any, callback: any) => any; + canSetProperty: typeof hasNoWriteLock; + canGetProperty: (arg: any, resource: any, callback: any) => any; +} diff --git a/lib/user/v2/privilege/FakePrivilegeManager.js b/lib/user/v2/privilege/FakePrivilegeManager.js new file mode 100644 index 00000000..80f155d3 --- /dev/null +++ b/lib/user/v2/privilege/FakePrivilegeManager.js @@ -0,0 +1,36 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var SimplePrivilegeManager_1 = require("./SimplePrivilegeManager"); +var IPrivilegeManager_1 = require("./IPrivilegeManager"); +var FakePrivilegeManager = (function (_super) { + __extends(FakePrivilegeManager, _super); + function FakePrivilegeManager() { + var _this = _super.call(this) || this; + _this.canCreate = function (arg, resource, callback) { return callback(null, true); }; + _this.canDelete = IPrivilegeManager_1.hasNoWriteLock; + _this.canWrite = IPrivilegeManager_1.hasNoWriteLock; + _this.canSource = function (arg, resource, callback) { return callback(null, true); }; + _this.canRead = function (arg, resource, callback) { return callback(null, true); }; + _this.canListLocks = function (arg, resource, callback) { return callback(null, true); }; + _this.canSetLock = IPrivilegeManager_1.hasNoWriteLock; + _this.canGetAvailableLocks = function (arg, resource, callback) { return callback(null, true); }; + _this.canAddChild = IPrivilegeManager_1.hasNoWriteLock; + _this.canRemoveChild = IPrivilegeManager_1.hasNoWriteLock; + _this.canGetChildren = function (arg, resource, callback) { return callback(null, true); }; + _this.canSetProperty = IPrivilegeManager_1.hasNoWriteLock; + _this.canGetProperty = function (arg, resource, callback) { return callback(null, true); }; + return _this; + } + return FakePrivilegeManager; +}(SimplePrivilegeManager_1.SimplePrivilegeManager)); +exports.FakePrivilegeManager = FakePrivilegeManager; diff --git a/lib/user/v2/privilege/IPrivilegeManager.d.ts b/lib/user/v2/privilege/IPrivilegeManager.d.ts new file mode 100644 index 00000000..db1f96b3 --- /dev/null +++ b/lib/user/v2/privilege/IPrivilegeManager.d.ts @@ -0,0 +1,35 @@ +import { RequestContext } from '../../../server/v2/RequestContext'; +import { Resource } from '../../../manager/v2/export'; +export declare type PrivilegeManagerCallback = (error: Error, hasAccess: boolean) => void; +export declare type PrivilegeManagerMethod = (ctx: RequestContext, resource: Resource, callback: PrivilegeManagerCallback) => void; +export declare type BasicPrivilege = 'all' | 'canReadLocks' | 'canWriteLocks' | 'canWrite' | 'canRead' | 'canSee' | 'canReadProperties' | 'canWriteProperties'; +export declare function requirePrivilege(privilege: string | BasicPrivilege | string[] | BasicPrivilege[], ctx: RequestContext, resource: Resource, callback: PrivilegeManagerCallback): void; +export interface IPrivilegeManager { + canCreate: PrivilegeManagerMethod; + canDelete: PrivilegeManagerMethod; + canMove: PrivilegeManagerMethod; + canRename: PrivilegeManagerMethod; + canAppend: PrivilegeManagerMethod; + canWrite: PrivilegeManagerMethod; + canRead: PrivilegeManagerMethod; + canSource: PrivilegeManagerMethod; + canGetMimeType: PrivilegeManagerMethod; + canGetSize: PrivilegeManagerMethod; + canListLocks: PrivilegeManagerMethod; + canSetLock: PrivilegeManagerMethod; + canRemoveLock: PrivilegeManagerMethod; + canGetAvailableLocks: PrivilegeManagerMethod; + canGetLock: PrivilegeManagerMethod; + canAddChild: PrivilegeManagerMethod; + canRemoveChild: PrivilegeManagerMethod; + canGetChildren: PrivilegeManagerMethod; + canSetProperty: PrivilegeManagerMethod; + canGetProperty: PrivilegeManagerMethod; + canGetProperties: PrivilegeManagerMethod; + canRemoveProperty: PrivilegeManagerMethod; + canGetCreationDate: PrivilegeManagerMethod; + canGetLastModifiedDate: PrivilegeManagerMethod; + canGetWebName: PrivilegeManagerMethod; + canGetType: PrivilegeManagerMethod; +} +export declare function hasNoWriteLock(ctx: RequestContext, resource: Resource, callback: PrivilegeManagerCallback): void; diff --git a/lib/user/v2/privilege/IPrivilegeManager.js b/lib/user/v2/privilege/IPrivilegeManager.js new file mode 100644 index 00000000..c692bc85 --- /dev/null +++ b/lib/user/v2/privilege/IPrivilegeManager.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +/* +export type BasicPrivilege = + 'all' + | 'canCreate' + | 'canDelete' + | 'canMove' + | 'canRename' + | 'canAppend' + | 'canWrite' + | 'canRead' + | 'canSource' + | 'canGetMimeType' + | 'canGetSize' + | 'canListLocks' + | 'canSetLock' + | 'canRemoveLock' + | 'canGetAvailableLocks' + | 'canGetLock' + | 'canAddChild' + | 'canRemoveChild' + | 'canGetChildren' + | 'canSetProperty' + | 'canGetProperty' + | 'canGetProperties' + | 'canRemoveProperty' + | 'canGetCreationDate' + | 'canGetLastModifiedDate' + | 'canGetWebName' + | 'canGetType'; +*/ +function requirePrivilege(privilege, ctx, resource, callback) { +} +exports.requirePrivilege = requirePrivilege; +function hasNoWriteLock(ctx, resource, callback) { +} +exports.hasNoWriteLock = hasNoWriteLock; diff --git a/lib/user/v2/privilege/SimplePathPrivilegeManager.d.ts b/lib/user/v2/privilege/SimplePathPrivilegeManager.d.ts new file mode 100644 index 00000000..4bcb5f43 --- /dev/null +++ b/lib/user/v2/privilege/SimplePathPrivilegeManager.d.ts @@ -0,0 +1,24 @@ +import { SimplePrivilegeManager, SimpleBasicPrivilege } from './SimplePrivilegeManager'; +import { RequestContext } from '../../../server/v2/RequestContext'; +import { Resource } from '../../../manager/v2/export'; +import { IUser } from '../IUser'; +export declare class SimplePathPrivilegeManager extends SimplePrivilegeManager { + rights: any; + constructor(); + setRights(user: IUser, path: string, rights: SimpleBasicPrivilege[]): void; + getRights(user: IUser, path: string): SimpleBasicPrivilege[]; + can(user: IUser, path: string, right: SimpleBasicPrivilege): boolean; + canCreate: (ctx: RequestContext, resource: Resource, callback: any) => any; + canDelete: (ctx: RequestContext, resource: Resource, callback: any) => void; + canWrite: (ctx: RequestContext, resource: Resource, callback: any) => void; + canSource: (ctx: RequestContext, resource: Resource, callback: any) => any; + canRead: (ctx: RequestContext, resource: Resource, callback: any) => any; + canListLocks: (ctx: RequestContext, resource: Resource, callback: any) => any; + canSetLock: (ctx: RequestContext, resource: Resource, callback: any) => void; + canGetAvailableLocks: (ctx: RequestContext, resource: Resource, callback: any) => any; + canAddChild: (ctx: RequestContext, resource: Resource, callback: any) => void; + canRemoveChild: (ctx: RequestContext, resource: Resource, callback: any) => void; + canGetChildren: (ctx: RequestContext, resource: Resource, callback: any) => any; + canSetProperty: (ctx: RequestContext, resource: Resource, callback: any) => void; + canGetProperty: (ctx: RequestContext, resource: Resource, callback: any) => any; +} diff --git a/lib/user/v2/privilege/SimplePathPrivilegeManager.js b/lib/user/v2/privilege/SimplePathPrivilegeManager.js new file mode 100644 index 00000000..a3448648 --- /dev/null +++ b/lib/user/v2/privilege/SimplePathPrivilegeManager.js @@ -0,0 +1,81 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var SimplePrivilegeManager_1 = require("./SimplePrivilegeManager"); +var IPrivilegeManager_1 = require("./IPrivilegeManager"); +function standarizePath(path) { + if (!path) + path = '/'; + var startIndex = path.indexOf('://'); + if (startIndex !== -1) { + path = path.substr(startIndex + 3); + path = path.substr(path.indexOf('/') + 1); + } + path = path.replace(/\\/g, '/'); + var rex = /\/\//g; + while (rex.test(path)) + path = path.replace(rex, '/'); + path = path.replace(/\/$/g, ''); + path = path.replace(/^([^\/])/g, '/$1'); + if (path.length === 0) + path = '/'; + return path; +} +function checker(sppm, right) { + return function (ctx, resource, callback) { return callback(null, sppm.can(ctx.user, ctx.requested.uri, right)); }; +} +function checkerNoLock(sppm, right) { + return function (ctx, resource, callback) { + if (!sppm.can(ctx.user, ctx.requested.uri, right)) + callback(null, false); + else + IPrivilegeManager_1.hasNoWriteLock(ctx, resource, callback); + }; +} +var SimplePathPrivilegeManager = (function (_super) { + __extends(SimplePathPrivilegeManager, _super); + function SimplePathPrivilegeManager() { + var _this = _super.call(this) || this; + _this.canCreate = checker(_this, 'canCreate'); + _this.canDelete = checkerNoLock(_this, 'canDelete'); + _this.canWrite = checkerNoLock(_this, 'canWrite'); + _this.canSource = checker(_this, 'canSource'); + _this.canRead = checker(_this, 'canRead'); + _this.canListLocks = checker(_this, 'canListLocks'); + _this.canSetLock = checkerNoLock(_this, 'canSetLock'); + _this.canGetAvailableLocks = checker(_this, 'canGetAvailableLocks'); + _this.canAddChild = checkerNoLock(_this, 'canAddChild'); + _this.canRemoveChild = checkerNoLock(_this, 'canRemoveChild'); + _this.canGetChildren = checker(_this, 'canGetChildren'); + _this.canSetProperty = checkerNoLock(_this, 'canSetProperty'); + _this.canGetProperty = checker(_this, 'canGetProperty'); + _this.rights = {}; + return _this; + } + SimplePathPrivilegeManager.prototype.setRights = function (user, path, rights) { + if (!this.rights[user.uid]) + this.rights[user.uid] = {}; + this.rights[user.uid][standarizePath(path)] = rights; + }; + SimplePathPrivilegeManager.prototype.getRights = function (user, path) { + if (!this.rights[user.uid]) + return []; + return this.rights[user.uid][standarizePath(path)]; + }; + SimplePathPrivilegeManager.prototype.can = function (user, path, right) { + var rights = this.getRights(user, path); + var r = rights && (rights.indexOf('all') !== -1 || rights.indexOf(right) !== -1); + return r; + }; + return SimplePathPrivilegeManager; +}(SimplePrivilegeManager_1.SimplePrivilegeManager)); +exports.SimplePathPrivilegeManager = SimplePathPrivilegeManager; diff --git a/lib/user/v2/privilege/SimplePrivilegeManager.d.ts b/lib/user/v2/privilege/SimplePrivilegeManager.d.ts new file mode 100644 index 00000000..9b4c58d5 --- /dev/null +++ b/lib/user/v2/privilege/SimplePrivilegeManager.d.ts @@ -0,0 +1,31 @@ +import { PrivilegeManagerMethod } from './IPrivilegeManager'; +import { IPrivilegeManager } from './IPrivilegeManager'; +export declare type SimpleBasicPrivilege = 'all' | 'canCreate' | 'canDelete' | 'canWrite' | 'canSource' | 'canRead' | 'canListLocks' | 'canSetLock' | 'canGetAvailableLocks' | 'canAddChild' | 'canRemoveChild' | 'canGetChildren' | 'canSetProperty' | 'canGetProperty'; +export declare abstract class SimplePrivilegeManager implements IPrivilegeManager { + abstract canCreate: PrivilegeManagerMethod; + abstract canDelete: PrivilegeManagerMethod; + canMove: (ctx: any, resource: any, callback: any) => void; + canRename: (ctx: any, resource: any, callback: any) => void; + canAppend: (ctx: any, resource: any, callback: any) => void; + abstract canWrite: PrivilegeManagerMethod; + abstract canRead: PrivilegeManagerMethod; + abstract canSource: PrivilegeManagerMethod; + canGetMimeType: (ctx: any, resource: any, callback: any) => void; + canGetSize: (ctx: any, resource: any, callback: any) => void; + abstract canListLocks: PrivilegeManagerMethod; + abstract canSetLock: PrivilegeManagerMethod; + canRemoveLock: (ctx: any, resource: any, callback: any) => void; + abstract canGetAvailableLocks: PrivilegeManagerMethod; + canGetLock: (ctx: any, resource: any, callback: any) => void; + abstract canAddChild: PrivilegeManagerMethod; + abstract canRemoveChild: PrivilegeManagerMethod; + abstract canGetChildren: PrivilegeManagerMethod; + abstract canSetProperty: PrivilegeManagerMethod; + abstract canGetProperty: PrivilegeManagerMethod; + canGetProperties: (ctx: any, resource: any, callback: any) => void; + canRemoveProperty: (ctx: any, resource: any, callback: any) => void; + canGetCreationDate: (ctx: any, resource: any, callback: any) => void; + canGetLastModifiedDate: (ctx: any, resource: any, callback: any) => void; + canGetWebName: (ctx: any, resource: any, callback: any) => void; + canGetType: (ctx: any, resource: any, callback: any) => void; +} diff --git a/lib/user/v2/privilege/SimplePrivilegeManager.js b/lib/user/v2/privilege/SimplePrivilegeManager.js new file mode 100644 index 00000000..c0823ac5 --- /dev/null +++ b/lib/user/v2/privilege/SimplePrivilegeManager.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SimplePrivilegeManager = (function () { + function SimplePrivilegeManager() { + var _this = this; + this.canMove = function (ctx, resource, callback) { + _this.canDelete(ctx, resource, function (e, v) { + if (e || !v) + callback(e, v); + else + _this.canRead(ctx, resource, callback); + }); + }; + this.canRename = function (ctx, resource, callback) { return _this.canWrite(ctx, resource, callback); }; + this.canAppend = function (ctx, resource, callback) { return _this.canWrite(ctx, resource, callback); }; + this.canGetMimeType = function (ctx, resource, callback) { return _this.canRead(ctx, resource, callback); }; + this.canGetSize = function (ctx, resource, callback) { return _this.canRead(ctx, resource, callback); }; + this.canRemoveLock = function (ctx, resource, callback) { return _this.canSetLock(ctx, resource, callback); }; + this.canGetLock = function (ctx, resource, callback) { return _this.canListLocks(ctx, resource, callback); }; + this.canGetProperties = function (ctx, resource, callback) { return _this.canGetProperty(ctx, resource, callback); }; + this.canRemoveProperty = function (ctx, resource, callback) { return _this.canSetProperty(ctx, resource, callback); }; + this.canGetCreationDate = function (ctx, resource, callback) { return _this.canRead(ctx, resource, callback); }; + this.canGetLastModifiedDate = function (ctx, resource, callback) { return _this.canRead(ctx, resource, callback); }; + this.canGetWebName = function (ctx, resource, callback) { return _this.canRead(ctx, resource, callback); }; + this.canGetType = function (ctx, resource, callback) { return _this.canRead(ctx, resource, callback); }; + } + return SimplePrivilegeManager; +}()); +exports.SimplePrivilegeManager = SimplePrivilegeManager; diff --git a/lib/user/v2/simple/SimpleUser.d.ts b/lib/user/v2/simple/SimpleUser.d.ts new file mode 100644 index 00000000..09421c07 --- /dev/null +++ b/lib/user/v2/simple/SimpleUser.d.ts @@ -0,0 +1,9 @@ +import { IUser } from '../IUser'; +export declare class SimpleUser implements IUser { + username: string; + password: string; + isAdministrator: boolean; + isDefaultUser: boolean; + uid: string; + constructor(username: string, password: string, isAdministrator: boolean, isDefaultUser: boolean); +} diff --git a/lib/user/v2/simple/SimpleUser.js b/lib/user/v2/simple/SimpleUser.js new file mode 100644 index 00000000..3c4026d3 --- /dev/null +++ b/lib/user/v2/simple/SimpleUser.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SimpleUser = (function () { + function SimpleUser(username, password, isAdministrator, isDefaultUser) { + this.username = username; + this.password = password; + this.isAdministrator = isAdministrator; + this.isDefaultUser = isDefaultUser; + this.uid = username; + } + return SimpleUser; +}()); +exports.SimpleUser = SimpleUser; diff --git a/lib/user/v2/simple/SimpleUserManager.d.ts b/lib/user/v2/simple/SimpleUserManager.d.ts new file mode 100644 index 00000000..f3407f68 --- /dev/null +++ b/lib/user/v2/simple/SimpleUserManager.d.ts @@ -0,0 +1,12 @@ +import { IListUserManager } from '../userManager/IListUserManager'; +import { ITestableUserManager } from '../userManager/ITestableUserManager'; +import { IUser } from '../IUser'; +export declare class SimpleUserManager implements ITestableUserManager, IListUserManager { + protected users: any; + constructor(); + getUserByName(name: string, callback: (error: Error, user?: IUser) => void): void; + getDefaultUser(callback: (user: IUser) => void): void; + addUser(name: string, password: string, isAdmin?: boolean): IUser; + getUsers(callback: (error: Error, users: IUser[]) => void): void; + getUserByNamePassword(name: string, password: string, callback: (error: Error, user?: IUser) => void): void; +} diff --git a/lib/user/v2/simple/SimpleUserManager.js b/lib/user/v2/simple/SimpleUserManager.js new file mode 100644 index 00000000..e286928c --- /dev/null +++ b/lib/user/v2/simple/SimpleUserManager.js @@ -0,0 +1,44 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var SimpleUser_1 = require("./SimpleUser"); +var Errors_1 = require("../../../Errors"); +var SimpleUserManager = (function () { + function SimpleUserManager() { + this.users = { + __default: new SimpleUser_1.SimpleUser('DefaultUser', null, false, true) + }; + } + SimpleUserManager.prototype.getUserByName = function (name, callback) { + if (!this.users[name]) + callback(Errors_1.Errors.UserNotFound); + else + callback(null, this.users[name]); + }; + SimpleUserManager.prototype.getDefaultUser = function (callback) { + callback(this.users.__default); + }; + SimpleUserManager.prototype.addUser = function (name, password, isAdmin) { + if (isAdmin === void 0) { isAdmin = false; } + var user = new SimpleUser_1.SimpleUser(name, password, isAdmin, false); + this.users[name] = user; + return user; + }; + SimpleUserManager.prototype.getUsers = function (callback) { + var users = []; + for (var name_1 in this.users) + users.push(this.users[name_1]); + callback(null, users); + }; + SimpleUserManager.prototype.getUserByNamePassword = function (name, password, callback) { + this.getUserByName(name, function (e, user) { + if (e) + return callback(e); + if (user.password === password) + callback(null, user); + else + callback(Errors_1.Errors.UserNotFound); + }); + }; + return SimpleUserManager; +}()); +exports.SimpleUserManager = SimpleUserManager; diff --git a/lib/user/v2/userManager/IListUserManager.d.ts b/lib/user/v2/userManager/IListUserManager.d.ts new file mode 100644 index 00000000..a7a16bbe --- /dev/null +++ b/lib/user/v2/userManager/IListUserManager.d.ts @@ -0,0 +1,6 @@ +import { IUser } from '../IUser'; +export interface IListUserManager { + getUserByName(name: string, callback: (error: Error, user?: IUser) => void): any; + getDefaultUser(callback: (user: IUser) => void): any; + getUsers(callback: (error: Error, users?: IUser[]) => void): any; +} diff --git a/lib/user/v2/userManager/IListUserManager.js b/lib/user/v2/userManager/IListUserManager.js new file mode 100644 index 00000000..c8ad2e54 --- /dev/null +++ b/lib/user/v2/userManager/IListUserManager.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/lib/user/v2/userManager/ITestableUserManager.d.ts b/lib/user/v2/userManager/ITestableUserManager.d.ts new file mode 100644 index 00000000..bb76e721 --- /dev/null +++ b/lib/user/v2/userManager/ITestableUserManager.d.ts @@ -0,0 +1,5 @@ +import { IUser } from '../IUser'; +export interface ITestableUserManager { + getDefaultUser(callback: (user: IUser) => void): any; + getUserByNamePassword(name: string, password: string, callback: (error: Error, user?: IUser) => void): any; +} diff --git a/lib/user/v2/userManager/ITestableUserManager.js b/lib/user/v2/userManager/ITestableUserManager.js new file mode 100644 index 00000000..c8ad2e54 --- /dev/null +++ b/lib/user/v2/userManager/ITestableUserManager.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/src/helper/v2/IfParser.ts b/src/helper/v2/IfParser.ts new file mode 100644 index 00000000..3ee9bbe2 --- /dev/null +++ b/src/helper/v2/IfParser.ts @@ -0,0 +1,183 @@ +import { ReturnCallback, ETag } from '../../resource/IResource' +import { RequestContext } from '../../server/v2/RequestContext' +import { Errors } from '../../Errors' +import { FileSystem } from '../../manager/v2/fileSystem/FileSystem' +import { ContextualFileSystem } from '../../manager/v2/fileSystem/ContextualFileSystem' +import { Resource } from '../../manager/v2/fileSystem/Resource' +import { Path } from '../../manager/v2/Path' +import * as url from 'url' + +type FnReturn = ReturnCallback + +function NoLock() +{ + return function(resource : Resource, callback : FnReturn) { + resource.lockManager((e, lm) => { + if(e) + return callback(e, false); + + lm.getLocks((e, locks) => { + callback(e, locks ? locks.length === 0 : false); + }) + }) + } +} + +function Token(token : string) +{ + return function(resource : Resource, callback : FnReturn) { + resource.lockManager((e, lm) => { + if(e) + return callback(e, false); + + lm.getLock(token, (e, lock) => callback(e, !!lock && !e)); + }) + } +} + +function Tag(tag : string) +{ + return function(resource : Resource, callback : FnReturn) { + resource.etag((e, etag) => callback(e, !e && etag === tag)); + } +} + +function Not(filter) +{ + return function(resource : Resource, callback : FnReturn) { + filter(resource, (e, v) => { + callback(e, !v); + }) + } +} + +function parseInternal(group : string) +{ + const rex = /((not)|<([^>]+)>|\[([^\]]+)\]|<(DAV:no-lock)>)/ig; + let match = rex.exec(group); + + let isNot = false; + const andArray = []; + function add(filter) + { + andArray.push(isNot ? Not(filter) : filter); + isNot = false; + } + + while(match) + { + if(match[2]) + { // not + isNot = true; + } + else if(match[3]) + { // lock-token + add(Token(match[3])); + } + else if(match[4]) + { // tag + add(Tag(match[4])); + } + else if(match[5]) + { // DAV:no-lock + add(NoLock()); + } + match = rex.exec(group); + } + + if(andArray.length) + return (r, callback) => callback(null, true); + + return function(resource : Resource, callback : FnReturn) { + let nb = andArray.length; + function done(error, result) + { + if(nb <= 0) + return; + if(error) + { + nb = -1; + callback(error, false); + return; + } + --nb; + if(nb === 0 || !result) + { + nb = -1; + callback(null, result); + } + } + + andArray.forEach((a) => a(resource, done)); + }; +} + +export function extractOneToken(ifHeader : string) +{ + const match = /^[ ]*\([ ]*<([^>]+)>[ ]*\)[ ]*$/.exec(ifHeader); + if(!match) + return null; + else + return match[1]; +} + +export function parseIfHeader(ifHeader : string) +{ + const rex = /(?:<([^>]+)>)?\s*\(([^\)]+)\)/g; + let match = rex.exec(ifHeader); + + const orArray : { + path : string, + actions : (resource : Resource, callback : FnReturn) => void + }[] = []; + let oldPath = undefined; + + while(match) + { + if(match[1]) + oldPath = url.parse(match[1]).path; + + orArray.push({ + path: oldPath, + actions: parseInternal(match[2]) + }) + + match = rex.exec(ifHeader); + } + + if(orArray.length == 0) + return (ctx : RequestContext, resource : Resource, callback : ReturnCallback) => callback(null, true); + + return function(ctx : RequestContext, resource : Resource, callback : ReturnCallback) { + let nb = orArray.length; + function done(error, result) + { + if(nb <= 0) + return; + if(error) + { + nb = -1; + callback(error, false); + return; + } + --nb; + if(nb === 0 || result) + { + nb = -1; + callback(null, result); + } + } + + orArray.forEach((a) => { + if(!a.path) + a.actions(resource, done); + else + { + const sPath = new Path(a.path); + ctx.server.getFileSystem(sPath, (fs, _, sub) => { + a.actions(fs.resource(ctx, sPath), done); + }) + } + }) + } +} diff --git a/src/helper/v2/export.ts b/src/helper/v2/export.ts new file mode 100644 index 00000000..0f232b9c --- /dev/null +++ b/src/helper/v2/export.ts @@ -0,0 +1,4 @@ + +export * from './IfParser' +export * from '../Workflow' +export * from '../XML' diff --git a/src/manager/v2/Path.ts b/src/manager/v2/Path.ts new file mode 100644 index 00000000..0d9f77f3 --- /dev/null +++ b/src/manager/v2/Path.ts @@ -0,0 +1,86 @@ + +export class Path +{ + paths : string[] + + constructor(path : Path | string[] | string) + { + if(path.constructor === String) + { + let sPath = decodeURI((path as string)); + let doubleIndex; + while((doubleIndex = sPath.indexOf('//')) !== -1) + sPath = sPath.substr(0, doubleIndex) + sPath.substr(doubleIndex + 1); + this.paths = sPath.replace(/(^\/|\/$)/g, '').split('/'); + } + else if(path.constructor === Path) + this.paths = (path as Path).paths.filter((x) => true); // clone + else + this.paths = path as string[]; + + this.paths = this.paths.filter((p) => p.length > 0); + } + + isRoot() : boolean + { + return this.paths.length === 0 || this.paths.length === 1 && this.paths[0].length === 0; + } + + fileName() : string + { + return this.paths[this.paths.length - 1]; + } + + rootName() : string + { + return this.paths[0]; + } + + parentName() : string + { + return this.paths[this.paths.length - 2]; + } + + getParent() : Path + { + return new Path(this.paths.slice(0, this.paths.length - 1)); + } + + hasParent() : boolean + { + return this.paths.length >= 2; + } + + removeRoot() : string + { + return this.paths.shift(); + } + + removeFile() : string + { + return this.paths.pop(); + } + + getChildPath(childPath : string | Path) : Path + { + const subPath = new Path(childPath); + + const path = this.clone(); + for(const subName of subPath.paths) + path.paths.push(subName); + return path; + } + + clone() : Path + { + return new Path(this); + } + + toString(endsWithSlash : boolean = false) : string + { + const value = '/' + this.paths.join('/'); + if(endsWithSlash && value.length > 1) + return value + '/'; + return value; + } +} diff --git a/src/manager/v2/export.ts b/src/manager/v2/export.ts new file mode 100644 index 00000000..bfb19b93 --- /dev/null +++ b/src/manager/v2/export.ts @@ -0,0 +1,6 @@ + +export * from './instances/FTPFileSystem' +export * from './instances/PhysicalFileSystem' +export * from './instances/VirtualFileSystem' +export * from './fileSystem/export' +export * from './Path' diff --git a/src/manager/v2/fileSystem/CommonTypes.ts b/src/manager/v2/fileSystem/CommonTypes.ts new file mode 100644 index 00000000..5de1f081 --- /dev/null +++ b/src/manager/v2/fileSystem/CommonTypes.ts @@ -0,0 +1,38 @@ +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { XMLElement } from '../../../helper/XML' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockType } from '../../../resource/lock/LockType' +import { LockKind } from '../../../resource/lock/LockKind' +import { Workflow } from '../../../helper/Workflow' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { Path } from '../Path' +import { ReturnCallback, SimpleCallback } from './CommonTypes' +import * as mimeTypes from 'mime-types' +import * as crypto from 'crypto' + +export type SimpleCallback = (error ?: Error) => void; +export type ReturnCallback = (error ?: Error, data ?: T) => void; +export type Return2Callback = (error ?: Error, data1 ?: T1, data2 ?: T2) => void; + +export type ResourcePropertyValue = string | XMLElement | XMLElement[] + +export class ResourceType +{ + static File = new ResourceType(true, false) + static Directory = new ResourceType(false, true) + + static Hybrid = new ResourceType(true, true) + static NoResource = new ResourceType(false, false) + + constructor(public isFile : boolean, public isDirectory : boolean) + { } +} + +export type OpenWriteStreamMode = 'mustCreate' | 'canCreate' | 'mustExist' | 'canCreateIntermediates' | 'mustCreateIntermediates'; + +export interface SubTree +{ + [name : string] : ResourceType | SubTree +} diff --git a/src/manager/v2/fileSystem/ContextInfo.ts b/src/manager/v2/fileSystem/ContextInfo.ts new file mode 100644 index 00000000..01506122 --- /dev/null +++ b/src/manager/v2/fileSystem/ContextInfo.ts @@ -0,0 +1,66 @@ +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { XMLElement } from '../../../helper/XML' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockType } from '../../../resource/lock/LockType' +import { LockKind } from '../../../resource/lock/LockKind' +import { Workflow } from '../../../helper/Workflow' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { Path } from '../Path' +import { ResourceType, OpenWriteStreamMode } from './CommonTypes' +import * as mimeTypes from 'mime-types' +import * as crypto from 'crypto' + +export interface IContextInfo +{ + context : RequestContext +} + +export interface OpenWriteStreamInfo extends IContextInfo +{ + targetSource : boolean + estimatedSize : number + mode : OpenWriteStreamMode +} +export interface OpenReadStreamInfo extends IContextInfo +{ + targetSource : boolean + estimatedSize : number +} +export interface MimeTypeInfo extends IContextInfo +{ + targetSource : boolean +} +export interface SizeInfo extends IContextInfo +{ + targetSource : boolean +} +export interface CreateInfo extends IContextInfo +{ + type : ResourceType +} +export interface CopyInfo extends IContextInfo +{ + depth : number + overwrite : boolean +} +export interface DeleteInfo extends IContextInfo +{ + depth : number +} +export interface MoveInfo extends IContextInfo +{ + overwrite : boolean +} +export interface ETagInfo extends IContextInfo { } +export interface RenameInfo extends IContextInfo { } +export interface AvailableLocksInfo extends IContextInfo { } +export interface LockManagerInfo extends IContextInfo { } +export interface PropertyManagerInfo extends IContextInfo { } +export interface ReadDirInfo extends IContextInfo { } +export interface CreationDateInfo extends IContextInfo { } +export interface LastModifiedDateInfo extends IContextInfo { } +export interface WebNameInfo extends IContextInfo { } +export interface DisplayNameInfo extends IContextInfo { } +export interface TypeInfo extends IContextInfo { } diff --git a/src/manager/v2/fileSystem/ContextualFileSystem.ts b/src/manager/v2/fileSystem/ContextualFileSystem.ts new file mode 100644 index 00000000..d0d41149 --- /dev/null +++ b/src/manager/v2/fileSystem/ContextualFileSystem.ts @@ -0,0 +1,166 @@ +import { AvailableLocksInfo, CopyInfo, CreateInfo, CreationDateInfo, DeleteInfo, DisplayNameInfo, ETagInfo, IContextInfo, LastModifiedDateInfo, LockManagerInfo, MimeTypeInfo, MoveInfo, OpenReadStreamInfo, OpenWriteStreamInfo, PropertyManagerInfo, ReadDirInfo, RenameInfo, SizeInfo, TypeInfo, WebNameInfo } from './ContextInfo' +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { XMLElement } from '../../../helper/XML' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockType } from '../../../resource/lock/LockType' +import { LockKind } from '../../../resource/lock/LockKind' +import { Workflow } from '../../../helper/Workflow' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { Path } from '../Path' +import { ReturnCallback, SimpleCallback, Return2Callback, OpenWriteStreamMode, SubTree, ResourceType } from './CommonTypes' +import { FileSystemSerializer, ISerializableFileSystem } from './Serialization' +import { FileSystem } from './FileSystem' +import { Resource } from './Resource' +import { IPropertyManager } from './PropertyManager' +import { ILockManager } from './LockManager' +import * as mimeTypes from 'mime-types' +import * as crypto from 'crypto' + +export class ContextualFileSystem implements ISerializableFileSystem +{ + constructor(public fs : FileSystem, public context : RequestContext) + { } + + resource(path : Path) : Resource + { + return new Resource(path, this.fs, this.context); + } + + delete(path : Path, callback : SimpleCallback) : void + delete(path : Path, depth : number, callback : SimpleCallback) : void + delete(path : Path, _depth : any, _callback ?: SimpleCallback) : void + { + this.fs.delete(this.context, path, _depth, _callback); + } + + openWriteStream(path : Path, callback : Return2Callback) : void + openWriteStream(path : Path, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(path : Path, targetSource : boolean, callback : Return2Callback) : void + openWriteStream(path : Path, targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(path : Path, mode : OpenWriteStreamMode, callback : Return2Callback) : void + openWriteStream(path : Path, mode : OpenWriteStreamMode, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(path : Path, mode : OpenWriteStreamMode, targetSource : boolean, callback : Return2Callback) : void + openWriteStream(path : Path, mode : OpenWriteStreamMode, targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(path : Path, _mode : any, _targetSource ?: any, _estimatedSize ?: any, _callback ?: Return2Callback) : void + { + this.fs.openWriteStream(this.context, path, _mode, _targetSource, _estimatedSize, _callback); + } + + openReadStream(path : Path, callback : ReturnCallback) : void + openReadStream(path : Path, estimatedSize : number, callback : ReturnCallback) : void + openReadStream(path : Path, targetSource : boolean, callback : ReturnCallback) : void + openReadStream(path : Path, targetSource : boolean, estimatedSize : number, callback : ReturnCallback) : void + openReadStream(path : Path, _targetSource : any, _estimatedSize ?: any, _callback ?: ReturnCallback) : void + { + this.fs.openReadStream(this.context, path, _targetSource, _estimatedSize, _callback); + } + + copy(pathFrom : Path, pathTo : Path, callback : ReturnCallback) : void + copy(pathFrom : Path, pathTo : Path, depth : number, callback : ReturnCallback) : void + copy(pathFrom : Path, pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + copy(pathFrom : Path, pathTo : Path, overwrite : boolean, depth : number, callback : ReturnCallback) : void + copy(pathFrom : Path, pathTo : Path, _overwrite : any, _depth ?: any, _callback ?: ReturnCallback) : void + { + this.fs.copy(this.context, pathFrom, pathTo, _overwrite, _depth, _callback); + } + + mimeType(path : Path, callback : ReturnCallback) : void + mimeType(path : Path, targetSource : boolean, callback : ReturnCallback) : void + mimeType(path : Path, _targetSource : any, _callback ?: ReturnCallback) : void + { + this.fs.mimeType(this.context, path, _targetSource, _callback); + } + + size(path : Path, callback : ReturnCallback) : void + size(path : Path, targetSource : boolean, callback : ReturnCallback) : void + size(path : Path, _targetSource : any, _callback ?: ReturnCallback) : void + { + this.fs.size(this.context, path, _targetSource, _callback); + } + + addSubTree(rootPath : Path, subTree : SubTree, callback : SimpleCallback) + addSubTree(rootPath : Path, resourceType : ResourceType, callback : SimpleCallback) + addSubTree(rootPath : Path, tree : any, callback : SimpleCallback) + { + this.fs.size(this.context, rootPath, tree, callback); + } + + create(path : Path, type : ResourceType, callback : SimpleCallback) : void + create(path : Path, type : ResourceType, createIntermediates : boolean, callback : SimpleCallback) : void + create(path : Path, type : ResourceType, _createIntermediates : any, _callback ?: SimpleCallback) : void + { + this.fs.create(this.context, path, type, _createIntermediates, _callback); + } + etag(path : Path, callback : ReturnCallback) : void + { + this.fs.etag(this.context, path, callback); + } + move(pathFrom : Path, pathTo : Path, callback : ReturnCallback) : void + move(pathFrom : Path, pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + move(pathFrom : Path, pathTo : Path, _overwrite : any, _callback ?: ReturnCallback) : void + { + this.fs.move(this.context, pathFrom, pathTo, _overwrite, _callback); + } + rename(pathFrom : Path, newName : string, callback : ReturnCallback) : void + rename(pathFrom : Path, newName : string, overwrite : boolean, callback : ReturnCallback) : void + rename(pathFrom : Path, newName : string, _overwrite : any, _callback ?: ReturnCallback) : void + { + this.fs.rename(this.context, pathFrom, newName, _overwrite, _callback); + } + availableLocks(path : Path, callback : ReturnCallback) : void + { + this.fs.availableLocks(this.context, path, callback); + } + lockManager(path : Path, callback : ReturnCallback) : void + { + this.fs.lockManager(this.context, path, callback); + } + propertyManager(path : Path, callback : ReturnCallback) : void + { + this.fs.propertyManager(this.context, path, callback); + } + readDir(path : Path, callback : ReturnCallback) : void + readDir(path : Path, retrieveExternalFiles : boolean, callback : ReturnCallback) : void + readDir(path : Path, _retrieveExternalFiles : any, _callback ?: ReturnCallback) : void + { + this.fs.readDir(this.context, path, _retrieveExternalFiles, _callback); + } + creationDate(path : Path, callback : ReturnCallback) : void + { + this.fs.creationDate(this.context, path, callback); + } + lastModifiedDate(path : Path, callback : ReturnCallback) : void + { + this.fs.lastModifiedDate(this.context, path, callback); + } + webName(path : Path, callback : ReturnCallback) : void + { + this.fs.webName(this.context, path, callback); + } + displayName(path : Path, callback : ReturnCallback) : void + { + this.fs.displayName(this.context, path, callback); + } + type(path : Path, callback : ReturnCallback) : void + { + this.fs.type(this.context, path, callback); + } + + listDeepLocks(startPath : Path, callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(startPath : Path, depth : number, callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(startPath : Path, _depth : any, _callback ?: ReturnCallback<{ [path : string] : Lock[] }>) + { + this.fs.listDeepLocks(this.context, startPath, _depth, _callback); + } + + serializer() : FileSystemSerializer + { + return this.fs.serializer(); + } + serialize(callback : (serializedData : any) => void) : void + { + this.fs.serialize(callback); + } +} diff --git a/src/manager/v2/fileSystem/FileSystem.ts b/src/manager/v2/fileSystem/FileSystem.ts new file mode 100644 index 00000000..943bf2e9 --- /dev/null +++ b/src/manager/v2/fileSystem/FileSystem.ts @@ -0,0 +1,707 @@ +import { AvailableLocksInfo, CopyInfo, CreateInfo, CreationDateInfo, DeleteInfo, DisplayNameInfo, ETagInfo, IContextInfo, LastModifiedDateInfo, LockManagerInfo, MimeTypeInfo, MoveInfo, OpenReadStreamInfo, OpenWriteStreamInfo, PropertyManagerInfo, ReadDirInfo, RenameInfo, SizeInfo, TypeInfo, WebNameInfo } from './ContextInfo' +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { BasicPrivilege } from '../../../user/v2/privilege/IPrivilegeManager' +import { XMLElement } from '../../../helper/XML' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockType } from '../../../resource/lock/LockType' +import { LockKind } from '../../../resource/lock/LockKind' +import { Workflow } from '../../../helper/Workflow' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { Path } from '../Path' +import { ResourceType, SimpleCallback, Return2Callback, ReturnCallback, SubTree, OpenWriteStreamMode } from './CommonTypes' +import { ContextualFileSystem } from './ContextualFileSystem' +import { ILockManager } from './LockManager' +import { IPropertyManager } from './PropertyManager' +import { Resource } from './Resource' +import { StandardMethods } from './StandardMethods' +import { ISerializableFileSystem, FileSystemSerializer } from './Serialization' +import * as mimeTypes from 'mime-types' +import * as crypto from 'crypto' + +export abstract class FileSystem implements ISerializableFileSystem +{ + private __serializer; + + constructor(serializer : FileSystemSerializer) + { + this.__serializer = serializer; + } + + serializer() : FileSystemSerializer + { + return this.__serializer; + } + + contextualize(ctx : RequestContext) : ContextualFileSystem + { + return new ContextualFileSystem(this, ctx); + } + + resource(ctx : RequestContext, path : Path) : Resource + { + return new Resource(path, this, ctx); + } + + fastExistCheckEx(ctx : RequestContext, path : Path, errorCallback : SimpleCallback, callback : () => void) : void + { + if(!this._fastExistCheck) + return callback(); + + this._fastExistCheck(ctx, path, (exists) => { + if(!exists) + errorCallback(Errors.ResourceNotFound); + else + callback(); + }); + } + fastExistCheckExReverse(ctx : RequestContext, path : Path, errorCallback : SimpleCallback, callback : () => void) : void + { + if(!this._fastExistCheck) + return callback(); + + this._fastExistCheck(ctx, path, (exists) => { + if(exists) + errorCallback(Errors.ResourceAlreadyExists); + else + callback(); + }); + } + protected fastExistCheck(ctx : RequestContext, path : Path, callback : (exists : boolean) => void) : void + { + if(!this._fastExistCheck) + return callback(true); + + this._fastExistCheck(ctx, path, (exists) => callback(!!exists)); + } + protected _fastExistCheck?(ctx : RequestContext, path : Path, callback : (exists : boolean) => void) : void + + create(ctx : RequestContext, path : Path, type : ResourceType, callback : SimpleCallback) : void + create(ctx : RequestContext, path : Path, type : ResourceType, createIntermediates : boolean, callback : SimpleCallback) : void + create(ctx : RequestContext, path : Path, type : ResourceType, _createIntermediates : boolean | SimpleCallback, _callback ?: SimpleCallback) : void + { + const createIntermediates = _callback ? _createIntermediates as boolean : false; + const callback = _callback ? _callback : _createIntermediates as SimpleCallback; + + if(!this._create) + return callback(Errors.InvalidOperation); + + const go = () => { + this._create(path, { + context: ctx, + type + }, callback); + } + + this.fastExistCheckExReverse(ctx, path, callback, () => { + this.type(ctx, path.getParent(), (e, type) => { + if(e === Errors.ResourceNotFound) + { + if(!createIntermediates) + return callback(Errors.IntermediateResourceMissing); + + this.getFullPath(ctx, path, (e, fullPath) => { + if(e) + return callback(e); + + fullPath = fullPath.getParent(); + ctx.getResource(fullPath, (e, r) => { + if(e) + return callback(e); + + r.create(ResourceType.Directory, (e) => { + if(e && e !== Errors.ResourceAlreadyExists) + return callback(e); + + go(); + }) + }) + }) + return; + } + if(e) + return callback(e); + + if(!type.isDirectory) + return callback(Errors.WrongParentTypeForCreation); + + go(); + }) + }) + } + protected _create?(path : Path, ctx : CreateInfo, callback : SimpleCallback) : void + + etag(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._etag) + return this.lastModifiedDate(ctx, path, (e, date) => { + if(e) + return callback(e); + callback(null, '"' + crypto.createHash('md5').update(date.toString()).digest('hex') + '"'); + }) + + this._etag(path, { + context: ctx + }, callback); + }) + } + protected _etag?(path : Path, ctx : ETagInfo, callback : ReturnCallback) : void + + delete(ctx : RequestContext, path : Path, callback : SimpleCallback) : void + delete(ctx : RequestContext, path : Path, depth : number, callback : SimpleCallback) : void + delete(ctx : RequestContext, path : Path, _depth : number | SimpleCallback, _callback ?: SimpleCallback) : void + { + const depth = _callback ? _depth as number : -1; + const callback = _callback ? _callback : _depth as SimpleCallback; + + if(!this._delete) + return callback(Errors.InvalidOperation); + + this.fastExistCheckEx(ctx, path, callback, () => { + this._delete(path, { + context: ctx, + depth + }, callback); + }) + } + protected _delete?(path : Path, ctx : DeleteInfo, callback : SimpleCallback) : void + + openWriteStream(ctx : RequestContext, path : Path, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, targetSource : boolean, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, mode : OpenWriteStreamMode, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, mode : OpenWriteStreamMode, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, mode : OpenWriteStreamMode, targetSource : boolean, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, mode : OpenWriteStreamMode, targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, _mode : OpenWriteStreamMode | boolean | number | Return2Callback, _targetSource ?: boolean | number | Return2Callback, _estimatedSize ?: number | Return2Callback, _callback ?: Return2Callback) : void + { + let targetSource = true; + for(const obj of [ _mode, _targetSource ]) + if(obj && obj.constructor === Boolean) + targetSource = obj as boolean; + + let estimatedSize = -1; + for(const obj of [ _mode, _targetSource, _estimatedSize ]) + if(obj && obj.constructor === Number) + estimatedSize = obj as number; + + let callback; + for(const obj of [ _mode, _targetSource, _estimatedSize, _callback ]) + if(obj && obj.constructor === Function) + callback = obj as Return2Callback; + + const mode = _mode && _mode.constructor === String ? _mode as OpenWriteStreamMode : 'mustExist'; + let created = false; + + if(!this._openWriteStream) + return callback(Errors.InvalidOperation); + + const go = (callback : Return2Callback) => + { + this._openWriteStream(path, { + context: ctx, + estimatedSize, + targetSource, + mode + }, (e, wStream) => callback(e, wStream, created)); + } + + const createAndGo = (intermediates : boolean) => + { + this.create(ctx, path, ResourceType.File, intermediates, (e) => { + if(e) + return callback(e); + + created = true; + go(callback); + }) + } + + switch(mode) + { + case 'mustExist': + this.fastExistCheckEx(ctx, path, callback, () => go(callback)); + break; + + case 'mustCreateIntermediates': + case 'mustCreate': + createAndGo(mode === 'mustCreateIntermediates'); + break; + + case 'canCreateIntermediates': + case 'canCreate': + go((e, wStream) => { + if(e === Errors.ResourceNotFound) + createAndGo(mode === 'canCreateIntermediates'); + else + callback(e, wStream); + }) + break; + + default: + callback(Errors.IllegalArguments); + break; + } + } + protected _openWriteStream?(path : Path, ctx : OpenWriteStreamInfo, callback : ReturnCallback) : void + + openReadStream(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + openReadStream(ctx : RequestContext, path : Path, estimatedSize : number, callback : ReturnCallback) : void + openReadStream(ctx : RequestContext, path : Path, targetSource : boolean, callback : ReturnCallback) : void + openReadStream(ctx : RequestContext, path : Path, targetSource : boolean, estimatedSize : number, callback : ReturnCallback) : void + openReadStream(ctx : RequestContext, path : Path, _targetSource : boolean | number | ReturnCallback, _estimatedSize ?: number | ReturnCallback, _callback ?: ReturnCallback) : void + { + const targetSource = _targetSource.constructor === Boolean ? _targetSource as boolean : true; + const estimatedSize = _callback ? _estimatedSize as number : _estimatedSize ? _targetSource as number : -1; + const callback = _callback ? _callback : _estimatedSize ? _estimatedSize as ReturnCallback : _targetSource as ReturnCallback; + + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._openReadStream) + return callback(Errors.InvalidOperation); + + this._openReadStream(path, { + context: ctx, + estimatedSize, + targetSource + }, callback); + }) + } + protected _openReadStream?(path : Path, ctx : OpenReadStreamInfo, callback : ReturnCallback) : void + + move(ctx : RequestContext, pathFrom : Path, pathTo : Path, callback : ReturnCallback) : void + move(ctx : RequestContext, pathFrom : Path, pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + move(ctx : RequestContext, pathFrom : Path, pathTo : Path, _overwrite : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const callback = _callback ? _callback : _overwrite as ReturnCallback; + const overwrite = _callback ? _overwrite as boolean : false; + + const go = () => + { + if(this._move) + { + this._move(pathFrom, pathTo, { + context: ctx, + overwrite + }, callback); + return; + } + + StandardMethods.standardMove(ctx, pathFrom, this, pathTo, this, callback); + } + + this.fastExistCheckEx(ctx, pathFrom, callback, () => { + if(!overwrite) + this.fastExistCheckExReverse(ctx, pathTo, callback, go); + else + go(); + }) + } + protected _move?(pathFrom : Path, pathTo : Path, ctx : MoveInfo, callback : ReturnCallback) : void + + copy(ctx : RequestContext, pathFrom : Path, pathTo : Path, callback : ReturnCallback) : void + copy(ctx : RequestContext, pathFrom : Path, pathTo : Path, depth : number, callback : ReturnCallback) : void + copy(ctx : RequestContext, pathFrom : Path, pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + copy(ctx : RequestContext, pathFrom : Path, pathTo : Path, overwrite : boolean, depth : number, callback : ReturnCallback) : void + copy(ctx : RequestContext, pathFrom : Path, pathTo : Path, _overwrite : boolean | number | ReturnCallback, _depth ?: number | ReturnCallback, _callback ?: ReturnCallback) : void + { + const overwrite = _overwrite.constructor === Boolean ? _overwrite as boolean : false; + const depth = _callback ? _depth as number : !_depth ? -1 : _overwrite.constructor === Number ? _overwrite as number : -1; + const callback = _callback ? _callback : _depth ? _depth as ReturnCallback : _overwrite as ReturnCallback; + + if(this._copy) + { + const go = () => + { + this._copy(pathFrom, pathTo, { + context: ctx, + depth, + overwrite + }, callback); + } + + this.fastExistCheckEx(ctx, pathFrom, callback, () => { + if(!overwrite) + this.fastExistCheckExReverse(ctx, pathTo, callback, go); + else + go(); + }) + } + else + StandardMethods.standardCopy(ctx, pathFrom, this, pathTo, this, overwrite, depth, callback); + + } + protected _copy?(pathFrom : Path, pathTo : Path, ctx : CopyInfo, callback : ReturnCallback) : void + + rename(ctx : RequestContext, pathFrom : Path, newName : string, callback : ReturnCallback) : void + rename(ctx : RequestContext, pathFrom : Path, newName : string, overwrite : boolean, callback : ReturnCallback) : void + rename(ctx : RequestContext, pathFrom : Path, newName : string, _overwrite : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const overwrite = _callback ? _overwrite as boolean : false; + const callback = _callback ? _callback : _overwrite as ReturnCallback; + + if(pathFrom.isRoot()) + { + this.getFullPath(ctx, (e, fullPath) => { + if(fullPath.isRoot()) + return callback(Errors.InvalidOperation); + + const newPath = fullPath.getParent().getChildPath(newName); + ctx.server.getFileSystem(newPath, (fs, _, subPath) => { + const go = (overwritten : boolean) => + { + ctx.server.setFileSystem(newPath, this, (successed) => { + if(!successed) + return callback(Errors.InvalidOperation); + + ctx.server.removeFileSystem(fullPath, () => callback(null, overwritten)); + }) + } + + if(!subPath.isRoot()) + return go(false); + + if(!overwrite) + return callback(Errors.ResourceAlreadyExists); + + ctx.server.removeFileSystem(newPath, () => { + go(true); + }) + }) + }) + return; + } + + this.fastExistCheckEx(ctx, pathFrom, callback, () => { + this.fastExistCheckExReverse(ctx, pathFrom.getParent().getChildPath(newName), callback, () => { + if(this._rename) + { + this._rename(pathFrom, newName, { + context: ctx + }, callback); + return; + } + + this.move(ctx, pathFrom, pathFrom.getParent().getChildPath(newName), overwrite, callback); + }) + }) + } + protected _rename?(pathFrom : Path, newName : string, ctx : RenameInfo, callback : ReturnCallback) : void + + mimeType(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + mimeType(ctx : RequestContext, path : Path, targetSource : boolean, callback : ReturnCallback) : void + mimeType(ctx : RequestContext, path : Path, _targetSource : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const targetSource = _callback ? _targetSource as boolean : true; + const callback = _callback ? _callback : _targetSource as ReturnCallback; + + this.fastExistCheckEx(ctx, path, callback, () => { + if(this._mimeType) + { + this._mimeType(path, { + context: ctx, + targetSource + }, callback); + return; + } + + StandardMethods.standardMimeType(ctx, this, path, targetSource, callback); + }) + } + protected _mimeType?(path : Path, ctx : MimeTypeInfo, callback : ReturnCallback) : void + + size(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + size(ctx : RequestContext, path : Path, targetSource : boolean, callback : ReturnCallback) : void + size(ctx : RequestContext, path : Path, _targetSource : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const targetSource = _callback ? _targetSource as boolean : true; + const callback = _callback ? _callback : _targetSource as ReturnCallback; + + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._size) + return callback(null, 0); + + this._size(path, { + context: ctx, + targetSource + }, callback); + }) + } + protected _size?(path : Path, ctx : SizeInfo, callback : ReturnCallback) : void + + availableLocks(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._availableLocks) + return callback(null, [ + new LockKind(LockScope.Exclusive, LockType.Write), + new LockKind(LockScope.Shared, LockType.Write) + ]); + + this._availableLocks(path, { + context: ctx + }, callback); + }) + } + protected _availableLocks?(path : Path, ctx : AvailableLocksInfo, callback : ReturnCallback) : void + + lockManager(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + this._lockManager(path, { + context: ctx + }, callback); + }) + } + protected abstract _lockManager(path : Path, ctx : LockManagerInfo, callback : ReturnCallback) : void + + propertyManager(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + this._propertyManager(path, { + context: ctx + }, callback); + }) + } + protected abstract _propertyManager(path : Path, ctx : PropertyManagerInfo, callback : ReturnCallback) : void + + readDir(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + readDir(ctx : RequestContext, path : Path, retrieveExternalFiles : boolean, callback : ReturnCallback) : void + readDir(ctx : RequestContext, path : Path, _retrieveExternalFiles : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const retrieveExternalFiles = _callback ? _retrieveExternalFiles as boolean : false; + const callback = _callback ? _callback : _retrieveExternalFiles as ReturnCallback; + + this.fastExistCheckEx(ctx, path, callback, () => { + const next = (base : string[]) => { + if(!this._readDir) + return callback(null, base); + + this._readDir(path, { + context: ctx + }, (e, paths) => { + if(e) + return callback(e); + + if(paths.length === 0) + return callback(null, base); + + if(paths[0].constructor === String) + base = base.concat(paths as string[]); + else + base = base.concat((paths as Path[]).map((p) => p.fileName())); + + callback(null, base); + }); + } + + if(!retrieveExternalFiles) + return next([]); + + this.getFullPath(ctx, (e, thisFullPath) => { + if(e) + return callback(e); + + ctx.server.getChildFileSystems(thisFullPath.getChildPath(path), (fss) => { + next(fss.map((f) => f.path.fileName())); + }) + }) + }) + } + protected _readDir?(path : Path, ctx : ReadDirInfo, callback : ReturnCallback) : void + + creationDate(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._creationDate && !this._lastModifiedDate) + return callback(null, 0); + if(!this._creationDate) + return this.lastModifiedDate(ctx, path, callback); + + this._creationDate(path, { + context: ctx + }, callback); + }) + } + protected _creationDate?(path : Path, ctx : CreationDateInfo, callback : ReturnCallback) : void + + lastModifiedDate(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._creationDate && !this._lastModifiedDate) + return callback(null, 0); + if(!this._lastModifiedDate) + return this.creationDate(ctx, path, callback); + + this._lastModifiedDate(path, { + context: ctx + }, callback); + }) + } + protected _lastModifiedDate?(path : Path, ctx : LastModifiedDateInfo, callback : ReturnCallback) : void + + webName(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + if(path.isRoot()) + this.getFullPath(ctx, (e, path) => callback(e, e ? null : path.fileName())); + else + callback(null, path.fileName()); + }) + } + + displayName(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._displayName) + return this.webName(ctx, path, callback); + + this._displayName(path, { + context: ctx + }, callback); + }) + } + protected _displayName?(path : Path, ctx : DisplayNameInfo, callback : ReturnCallback) : void + + type(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + this._type(path, { + context: ctx + }, callback); + }) + } + protected abstract _type(path : Path, ctx : TypeInfo, callback : ReturnCallback) : void + + addSubTree(ctx : RequestContext, subTree : SubTree, callback : SimpleCallback) + addSubTree(ctx : RequestContext, resourceType : ResourceType, callback : SimpleCallback) + addSubTree(ctx : RequestContext, rootPath : Path, subTree : SubTree, callback : SimpleCallback) + addSubTree(ctx : RequestContext, rootPath : Path, resourceType : ResourceType, callback : SimpleCallback) + addSubTree(ctx : RequestContext, _rootPath : Path | SubTree | ResourceType | SimpleCallback, _tree : SubTree | ResourceType | SimpleCallback, _callback ?: SimpleCallback) + { + const callback = _callback ? _callback : _tree as SimpleCallback; + const tree = _callback ? _tree as SubTree | ResourceType : _rootPath as SubTree | ResourceType; + const rootPath = _callback ? _rootPath as Path : new Path('/'); + + if(tree.constructor === ResourceType) + { + this.create(ctx, rootPath, tree as ResourceType, callback); + } + else + { + new Workflow() + .each(Object.keys(tree), (name, cb) => { + const value = tree[name]; + if(value.constructor === ResourceType) + this.addSubTree(ctx, rootPath.getChildPath(name), value, cb) + else + this.addSubTree(ctx, rootPath.getChildPath(name), ResourceType.Directory, (e) => { + if(e) + return cb(e); + + this.addSubTree(ctx, rootPath.getChildPath(name), value, cb); + }) + }) + .error(callback) + .done(() => callback()); + } + } + + listDeepLocks(ctx : RequestContext, startPath : Path, callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(ctx : RequestContext, startPath : Path, depth : number, callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(ctx : RequestContext, startPath : Path, _depth : number | ReturnCallback<{ [path : string] : Lock[] }>, _callback ?: ReturnCallback<{ [path : string] : Lock[] }>) + { + const depth = _callback ? _depth as number : 0; + const callback = _callback ? _callback : _depth as ReturnCallback<{ [path : string] : Lock[] }>; + + this.lockManager(ctx, startPath, (e, lm) => { + if(e) + return callback(e); + + lm.getLocks((e, locks) => { + if(e) + return callback(e); + + if(depth != -1) + locks = locks.filter((f) => f.depth === -1 || f.depth >= depth); + + const go = (fs : FileSystem, parentPath : Path) => + { + const destDepth = depth === -1 ? -1 : depth + 1; + fs.listDeepLocks(ctx, parentPath, destDepth, (e, pLocks) => { + if(e) + return callback(e); + + if(locks && locks.length > 0) + pLocks[startPath.toString()] = locks; + callback(null, pLocks); + }) + } + + if(!startPath.isRoot()) + return go(this, startPath.getParent()); + + this.getFullPath(ctx, (e, fsPath) => { + if(e) + return callback(e); + + if(fsPath.isRoot()) + { + const result = {}; + if(locks && locks.length > 0) + result[startPath.toString()] = locks; + return callback(null, result); + } + + ctx.server.getFileSystem(fsPath.getParent(), (fs, _, subPath) => { + go(fs, subPath); + }) + }) + }) + }) + } + + getFullPath(ctx : RequestContext, callback : ReturnCallback) + getFullPath(ctx : RequestContext, path : Path, callback : ReturnCallback) + getFullPath(ctx : RequestContext, _path : Path | ReturnCallback, _callback ?: ReturnCallback) + { + const path = _callback ? _path as Path : undefined; + const callback = _callback ? _callback : _path as ReturnCallback; + + ctx.server.getFileSystemPath(this, (fsPath) => { + callback(null, path ? fsPath.getChildPath(path) : fsPath); + }) + } + + checkPrivilege(ctx : RequestContext, path : Path, privilege : BasicPrivilege, callback : ReturnCallback) + checkPrivilege(ctx : RequestContext, path : Path, privileges : BasicPrivilege[], callback : ReturnCallback) + checkPrivilege(ctx : RequestContext, path : Path, privilege : string, callback : ReturnCallback) + checkPrivilege(ctx : RequestContext, path : Path, privileges : string[], callback : ReturnCallback) + checkPrivilege(ctx : RequestContext, path : Path, privileges : string | string[], callback : ReturnCallback) + { + if(privileges.constructor === String) + privileges = [ privileges as string ]; + + const resource = this.resource(ctx, path); + new Workflow() + .each(privileges as string[], (privilege, cb) => { + if(!privilege) + return cb(null, true); + + const method = ctx.server.options.privilegeManager[privilege]; + if(!method) + return cb(null, true); + + method(ctx, resource, cb); + }) + .error((e) => callback(e, false)) + .done((successes) => callback(null, successes.every((s) => !!s))); + } + + serialize(callback : ReturnCallback) : void + { + this.serializer().serialize(this, callback); + } +} diff --git a/src/manager/v2/fileSystem/FileSystem.ts.old b/src/manager/v2/fileSystem/FileSystem.ts.old new file mode 100644 index 00000000..b30cb7df --- /dev/null +++ b/src/manager/v2/fileSystem/FileSystem.ts.old @@ -0,0 +1,1395 @@ +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { XMLElement } from '../../../helper/XML' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockType } from '../../../resource/lock/LockType' +import { LockKind } from '../../../resource/lock/LockKind' +import { Workflow } from '../../../helper/Workflow' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { Path } from '../Path' +import * as mimeTypes from 'mime-types' +import * as crypto from 'crypto' + +export type SimpleCallback = (error ?: Error) => void; +export type ReturnCallback = (error ?: Error, data ?: T) => void; +export type Return2Callback = (error ?: Error, data1 ?: T1, data2 ?: T2) => void; + +export type ResourcePropertyValue = string | XMLElement | XMLElement[] + +export interface IContextInfo +{ + context : RequestContext +} + +export interface OpenWriteStreamInfo extends IContextInfo +{ + targetSource : boolean + estimatedSize : number + mode : OpenWriteStreamMode +} +export interface OpenReadStreamInfo extends IContextInfo +{ + targetSource : boolean + estimatedSize : number +} +export interface MimeTypeInfo extends IContextInfo +{ + targetSource : boolean +} +export interface SizeInfo extends IContextInfo +{ + targetSource : boolean +} +export interface CreateInfo extends IContextInfo +{ + type : ResourceType +} +export interface CopyInfo extends IContextInfo +{ + depth : number + overwrite : boolean +} +export interface DeleteInfo extends IContextInfo +{ + depth : number +} +export interface MoveInfo extends IContextInfo +{ + overwrite : boolean +} +export interface ETagInfo extends IContextInfo { } +export interface RenameInfo extends IContextInfo { } +export interface AvailableLocksInfo extends IContextInfo { } +export interface LockManagerInfo extends IContextInfo { } +export interface PropertyManagerInfo extends IContextInfo { } +export interface ReadDirInfo extends IContextInfo { } +export interface CreationDateInfo extends IContextInfo { } +export interface LastModifiedDateInfo extends IContextInfo { } +export interface WebNameInfo extends IContextInfo { } +export interface DisplayNameInfo extends IContextInfo { } +export interface TypeInfo extends IContextInfo { } + +export class ResourceType +{ + static File = new ResourceType(true, false) + static Directory = new ResourceType(false, true) + + static Hybrid = new ResourceType(true, true) + static NoResource = new ResourceType(false, false) + + constructor(public isFile : boolean, public isDirectory : boolean) + { } +} + +export interface ILockManager +{ + getLocks(callback : ReturnCallback) : void + setLock(lock : Lock, callback : SimpleCallback) : void + removeLock(uuid : string, callback : ReturnCallback) : void + getLock(uuid : string, callback : ReturnCallback) : void + refresh(uuid : string, timeout : number, callback : ReturnCallback) : void +} +export class LocalLockManager implements ILockManager +{ + locks : Lock[] = []; + + getLocks(callback : ReturnCallback) : void + { + this.locks = this.locks.filter((lock) => !lock.expired()); + + callback(null, this.locks); + } + + setLock(lock : Lock, callback : SimpleCallback) : void + { + this.locks.push(lock); + callback(null); + } + + removeLock(uuid : string, callback : ReturnCallback) : void + { + for(let index = 0; index < this.locks.length; ++index) + if(this.locks[index].uuid === uuid) + { + this.locks.splice(index, 1); + return callback(null, true); + } + + callback(null, false); + } + + getLock(uuid : string, callback : ReturnCallback) : void + { + this.locks = this.locks.filter((lock) => !lock.expired()); + + for(const lock of this.locks) + if(lock.uuid === uuid) + return callback(null, lock); + + callback(); + } + + refresh(uuid : string, timeout : number, callback : ReturnCallback) : void + { + this.getLock(uuid, (e, lock) => { + if(e || !lock) + return callback(e); + + lock.refresh(timeout); + callback(null, lock); + }) + } +} + +export interface PropertyBag +{ + [name : string] : ResourcePropertyValue +} +export interface IPropertyManager +{ + setProperty(name : string, value : ResourcePropertyValue, callback : SimpleCallback) : void + getProperty(name : string, callback : ReturnCallback) : void + removeProperty(name : string, callback : SimpleCallback) : void + getProperties(callback : ReturnCallback, byCopy ?: boolean) : void +} +export class LocalPropertyManager implements IPropertyManager +{ + properties : { + [name : string] : ResourcePropertyValue + } = { }; + + setProperty(name : string, value : ResourcePropertyValue, callback : SimpleCallback) : void + { + this.properties[name] = value; + callback(null); + } + + getProperty(name : string, callback : ReturnCallback) : void + { + const property = this.properties[name]; + callback(property ? null : Errors.PropertyNotFound, property); + } + + removeProperty(name : string, callback : SimpleCallback) : void + { + delete this.properties[name]; + callback(null); + } + + getProperties(callback : ReturnCallback, byCopy : boolean = false) : void + { + callback(null, byCopy ? this.properties : JSON.parse(JSON.stringify(this.properties))); + } +} + +export abstract class StandardMethods +{ + public static standardMove(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, callback : ReturnCallback) : void + public static standardMove(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, overwrite : boolean, callback : ReturnCallback) : void + public static standardMove(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, _overwrite : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const callback = _callback ? _callback : _overwrite as ReturnCallback; + const overwrite = _callback ? _overwrite as boolean : false; + + const go = (fullPathFrom ?: Path) => + { + StandardMethods.standardCopy(ctx, subPathFrom, fsFrom, subPathTo, fsTo, overwrite, -1, (e, overwritten) => { + if(e) + return callback(e, overwritten); + + if(fullPathFrom) + { // subPathFrom.isRoot() === true + ctx.server.removeFileSystem(fullPathFrom, (nb) => { + callback(null, overwritten); + }) + return; + } + + fsFrom.delete(ctx, subPathFrom, -1, (e) => callback(e, overwritten)); + }) + } + + if(subPathFrom.isRoot()) + { + fsFrom.getFullPath(ctx, (e, fullPathFrom) => { + go(fullPathFrom); + }) + } + else + go(); + } + + + + public static standardCopy(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, callback : ReturnCallback) : void + public static standardCopy(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, depth : number, callback : ReturnCallback) : void + public static standardCopy(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, overwrite : boolean, callback : ReturnCallback) : void + public static standardCopy(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, overwrite : boolean, depth : number, callback : ReturnCallback) : void + public static standardCopy(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, _overwrite : boolean | number | ReturnCallback, _depth ?: number | ReturnCallback, _callback ?: ReturnCallback) : void + { + const overwrite = _overwrite.constructor === Boolean ? _overwrite as boolean : false; + const depth = _callback ? _depth as number : !_depth ? -1 : _overwrite.constructor === Number ? _overwrite as number : -1; + const callback = _callback ? _callback : _depth ? _depth as ReturnCallback : _overwrite as ReturnCallback; + + if(subPathFrom.isRoot()) + { + fsTo.getFullPath(ctx, subPathTo, (e, fullPathTo) => { + if(e) + return callback(e); + + let overwritten = false; + ctx.server.getResource(ctx, fullPathTo, (e, r) => { + if(e) + return callback(e); + + r.type((e, type) => { + if(!e) + overwritten = true; + + ctx.server.setFileSystem(fullPathTo, fsFrom, (success) => { + callback(null, overwritten); + }) + }) + }) + }) + + return; + } + + const go = () => + { + const copyProperties = (callback : SimpleCallback) => + { + fsFrom.propertyManager(ctx, subPathFrom, (e, pmFrom) => { + if(e) + return callback(e); + + fsTo.propertyManager(ctx, subPathTo, (e, pmTo) => { + if(e) + return callback(e); + + pmFrom.getProperties((e, props) => { + if(e) + return callback(e); + + new Workflow() + .each(Object.keys(props), (name, cb) => pmTo.setProperty(name, props[name], cb)) + .error(callback) + .done(() => callback()) + }) + }) + }) + } + + const reverse1 = (e : Error) => { + fsTo.delete(ctx, subPathTo, () => callback(e)); + }; + + const copyContent = (callback : SimpleCallback) => + { + fsFrom.openReadStream(ctx, subPathFrom, (e, rStream) => { + if(e) + return reverse1(e); + + fsTo.openWriteStream(ctx, subPathTo, (e, wStream) => { + if(e) + return reverse1(e); + + let _callback = (e) => + { + _callback = () => {}; + callback(e); + } + + rStream.pipe(wStream); + rStream.on('error', _callback) + wStream.on('error', _callback) + wStream.on('finish', () => { + _callback(null); + }) + }) + }) + } + + const copyChildren = (callback : SimpleCallback) => + { + fsFrom.readDir(ctx, subPathFrom, false, (e, files) => { + if(e) + callback(e); + + const subDepth = depth === -1 ? -1 : Math.max(0, depth - 1); + + new Workflow() + .each(files, (file, cb) => StandardMethods.standardCopy(ctx, subPathFrom.getChildPath(file), fsFrom, subPathTo.getChildPath(file), fsTo, overwrite, subDepth, (e) => cb(e))) + .error(callback) + .done(() => callback()); + }) + } + + fsFrom.type(ctx, subPathFrom, (e, type) => { + if(e) + return callback(e); + + let overwritten = false; + + const startCopy = () => + { + const fns = [ copyProperties ]; + + if(type.isDirectory && depth !== 0) + fns.push(copyChildren); + if(type.isFile) + fns.push(copyContent); + + new Workflow() + .each(fns, (fn, cb) => fn(cb)) + .error((e) => callback(e, overwritten)) + .done(() => callback(null, overwritten)); + } + + fsTo.create(ctx, subPathTo, type, (e) => { + if(e === Errors.ResourceAlreadyExists && overwrite) + { + fsTo.delete(ctx, subPathTo, -1, (e) => { + if(e) + return callback(e); + overwritten = true; + + fsTo.create(ctx, subPathTo, type, (e) => { + if(e) + return callback(e); + startCopy(); + }) + }) + return; + } + else if(e) + return callback(e); + + startCopy(); + }) + }) + } + + fsFrom.fastExistCheckEx(ctx, subPathFrom, callback, () => { + if(!overwrite) + fsTo.fastExistCheckExReverse(ctx, subPathTo, callback, go); + else + go(); + }) + } + + public static standardMimeType(ctx : RequestContext, fs : FileSystem, path : Path, targetSource : boolean, callback : ReturnCallback) + public static standardMimeType(ctx : RequestContext, fs : FileSystem, path : Path, targetSource : boolean, useWebName : boolean, callback : ReturnCallback) + public static standardMimeType(ctx : RequestContext, fs : FileSystem, path : Path, targetSource : boolean, defaultMimeType : string, callback : ReturnCallback) + public static standardMimeType(ctx : RequestContext, fs : FileSystem, path : Path, targetSource : boolean, defaultMimeType : string, useWebName : boolean, callback : ReturnCallback) + public static standardMimeType(ctx : RequestContext, fs : FileSystem, path : Path, targetSource : boolean, _defaultMimeType : boolean | string | ReturnCallback, _useWebName ?: boolean | ReturnCallback, _callback ?: ReturnCallback) + { + let callback; + let useWebName = false; + let defaultMimeType = 'application/octet-stream'; + + if(_defaultMimeType.constructor === Function) + { + callback = _defaultMimeType as ReturnCallback; + } + else if(_defaultMimeType.constructor === Boolean) + { + callback = _useWebName as ReturnCallback; + if(_defaultMimeType !== undefined && _defaultMimeType !== null) + useWebName = _defaultMimeType as boolean; + } + else + { + callback = _callback as ReturnCallback; + if(_useWebName !== undefined && _useWebName !== null) + useWebName = _useWebName as boolean; + if(_defaultMimeType !== undefined && _defaultMimeType !== null) + defaultMimeType = _defaultMimeType as string; + } + + fs.type(ctx, path, (e, type) => { + if(e) + return callback(e, null); + + if(type.isFile) + { + const fn = useWebName ? fs.webName : fs.displayName; + fn.bind(fs)(ctx, path, (e, name) => { + if(e) + callback(e, null); + else + { + const mt = mimeTypes.contentType(name); + callback(null, mt ? mt as string : defaultMimeType); + } + }) + } + else + callback(Errors.NoMimeTypeForAFolder, null); + }) + } +} + +export class ContextualFileSystem implements ISerializableFileSystem +{ + constructor(public fs : FileSystem, public context : RequestContext) + { } + + resource(path : Path) : Resource + { + return new Resource(path, this.fs, this.context); + } + + delete(path : Path, callback : SimpleCallback) : void + delete(path : Path, depth : number, callback : SimpleCallback) : void + delete(path : Path, _depth : any, _callback ?: SimpleCallback) : void + { + this.fs.delete(this.context, path, _depth, _callback); + } + + openWriteStream(path : Path, callback : Return2Callback) : void + openWriteStream(path : Path, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(path : Path, targetSource : boolean, callback : Return2Callback) : void + openWriteStream(path : Path, targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(path : Path, mode : OpenWriteStreamMode, callback : Return2Callback) : void + openWriteStream(path : Path, mode : OpenWriteStreamMode, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(path : Path, mode : OpenWriteStreamMode, targetSource : boolean, callback : Return2Callback) : void + openWriteStream(path : Path, mode : OpenWriteStreamMode, targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(path : Path, _mode : any, _targetSource ?: any, _estimatedSize ?: any, _callback ?: Return2Callback) : void + { + this.fs.openWriteStream(this.context, path, _mode, _targetSource, _estimatedSize, _callback); + } + + openReadStream(path : Path, callback : ReturnCallback) : void + openReadStream(path : Path, estimatedSize : number, callback : ReturnCallback) : void + openReadStream(path : Path, targetSource : boolean, callback : ReturnCallback) : void + openReadStream(path : Path, targetSource : boolean, estimatedSize : number, callback : ReturnCallback) : void + openReadStream(path : Path, _targetSource : any, _estimatedSize ?: any, _callback ?: ReturnCallback) : void + { + this.fs.openReadStream(this.context, path, _targetSource, _estimatedSize, _callback); + } + + copy(pathFrom : Path, pathTo : Path, callback : ReturnCallback) : void + copy(pathFrom : Path, pathTo : Path, depth : number, callback : ReturnCallback) : void + copy(pathFrom : Path, pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + copy(pathFrom : Path, pathTo : Path, overwrite : boolean, depth : number, callback : ReturnCallback) : void + copy(pathFrom : Path, pathTo : Path, _overwrite : any, _depth ?: any, _callback ?: ReturnCallback) : void + { + this.fs.copy(this.context, pathFrom, pathTo, _overwrite, _depth, _callback); + } + + mimeType(path : Path, callback : ReturnCallback) : void + mimeType(path : Path, targetSource : boolean, callback : ReturnCallback) : void + mimeType(path : Path, _targetSource : any, _callback ?: ReturnCallback) : void + { + this.fs.mimeType(this.context, path, _targetSource, _callback); + } + + size(path : Path, callback : ReturnCallback) : void + size(path : Path, targetSource : boolean, callback : ReturnCallback) : void + size(path : Path, _targetSource : any, _callback ?: ReturnCallback) : void + { + this.fs.size(this.context, path, _targetSource, _callback); + } + + addSubTree(rootPath : Path, subTree : SubTree, callback : SimpleCallback) + addSubTree(rootPath : Path, resourceType : ResourceType, callback : SimpleCallback) + addSubTree(rootPath : Path, tree : any, callback : SimpleCallback) + { + this.fs.size(this.context, rootPath, tree, callback); + } + + create(path : Path, type : ResourceType, callback : SimpleCallback) : void + create(path : Path, type : ResourceType, createIntermediates : boolean, callback : SimpleCallback) : void + create(path : Path, type : ResourceType, _createIntermediates : any, _callback ?: SimpleCallback) : void + { + this.fs.create(this.context, path, type, _createIntermediates, _callback); + } + etag(path : Path, callback : ReturnCallback) : void + { + this.fs.etag(this.context, path, callback); + } + move(pathFrom : Path, pathTo : Path, callback : ReturnCallback) : void + move(pathFrom : Path, pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + move(pathFrom : Path, pathTo : Path, _overwrite : any, _callback ?: ReturnCallback) : void + { + this.fs.move(this.context, pathFrom, pathTo, _overwrite, _callback); + } + rename(pathFrom : Path, newName : string, callback : SimpleCallback) : void + { + this.fs.rename(this.context, pathFrom, newName, callback); + } + availableLocks(path : Path, callback : ReturnCallback) : void + { + this.fs.availableLocks(this.context, path, callback); + } + lockManager(path : Path, callback : ReturnCallback) : void + { + this.fs.lockManager(this.context, path, callback); + } + propertyManager(path : Path, callback : ReturnCallback) : void + { + this.fs.propertyManager(this.context, path, callback); + } + readDir(path : Path, callback : ReturnCallback) : void + readDir(path : Path, retrieveExternalFiles : boolean, callback : ReturnCallback) : void + readDir(path : Path, _retrieveExternalFiles : any, _callback ?: ReturnCallback) : void + { + this.fs.readDir(this.context, path, _retrieveExternalFiles, _callback); + } + creationDate(path : Path, callback : ReturnCallback) : void + { + this.fs.creationDate(this.context, path, callback); + } + lastModifiedDate(path : Path, callback : ReturnCallback) : void + { + this.fs.lastModifiedDate(this.context, path, callback); + } + webName(path : Path, callback : ReturnCallback) : void + { + this.fs.webName(this.context, path, callback); + } + displayName(path : Path, callback : ReturnCallback) : void + { + this.fs.displayName(this.context, path, callback); + } + type(path : Path, callback : ReturnCallback) : void + { + this.fs.type(this.context, path, callback); + } + + listDeepLocks(startPath : Path, callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(startPath : Path, depth : number, callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(startPath : Path, _depth : any, _callback ?: ReturnCallback<{ [path : string] : Lock[] }>) + { + this.fs.listDeepLocks(this.context, startPath, _depth, _callback); + } + + serializer() : FileSystemSerializer + { + return this.fs.serializer(); + } + serialize(callback : (serializedData : any) => void) : void + { + this.fs.serialize(callback); + } +} + +export class Resource +{ + constructor(public path : Path, public fs : FileSystem, public context : RequestContext) + { } + + delete(callback : SimpleCallback) : void + delete(depth : number, callback : SimpleCallback) : void + delete(_depth : any, _callback ?: SimpleCallback) : void + { + this.fs.delete(this.context, this.path, _depth, _callback); + } + + openWriteStream(callback : Return2Callback) : void + openWriteStream(estimatedSize : number, callback : Return2Callback) : void + openWriteStream(targetSource : boolean, callback : Return2Callback) : void + openWriteStream(targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(mode : OpenWriteStreamMode, callback : Return2Callback) : void + openWriteStream(mode : OpenWriteStreamMode, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(mode : OpenWriteStreamMode, targetSource : boolean, callback : Return2Callback) : void + openWriteStream(mode : OpenWriteStreamMode, targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(_mode : any, _targetSource ?: any, _estimatedSize ?: any, _callback ?: Return2Callback) : void + { + this.fs.openWriteStream(this.context, this.path, _mode, _targetSource, _estimatedSize, _callback); + } + + openReadStream(callback : ReturnCallback) : void + openReadStream(estimatedSize : number, callback : ReturnCallback) : void + openReadStream(targetSource : boolean, callback : ReturnCallback) : void + openReadStream(targetSource : boolean, estimatedSize : number, callback : ReturnCallback) : void + openReadStream(_targetSource : any, _estimatedSize ?: any, _callback ?: ReturnCallback) : void + { + this.fs.openReadStream(this.context, this.path, _targetSource, _estimatedSize, _callback); + } + + copy(pathTo : Path, callback : ReturnCallback) : void + copy(pathTo : Path, depth : number, callback : ReturnCallback) : void + copy(pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + copy(pathTo : Path, overwrite : boolean, depth : number, callback : ReturnCallback) : void + copy(pathTo : Path, _overwrite : any, _depth ?: any, _callback ?: ReturnCallback) : void + { + this.fs.copy(this.context, this.path, pathTo, _overwrite, _depth, _callback); + } + + mimeType(callback : ReturnCallback) : void + mimeType(targetSource : boolean, callback : ReturnCallback) : void + mimeType(_targetSource : any, _callback ?: ReturnCallback) : void + { + this.fs.mimeType(this.context, this.path, _targetSource, _callback); + } + + size(callback : ReturnCallback) : void + size(targetSource : boolean, callback : ReturnCallback) : void + size(_targetSource : any, _callback ?: ReturnCallback) : void + { + this.fs.size(this.context, this.path, _targetSource, _callback); + } + + addSubTree(subTree : SubTree, callback : SimpleCallback) + addSubTree(resourceType : ResourceType, callback : SimpleCallback) + addSubTree(tree : any, callback : SimpleCallback) + { + this.fs.addSubTree(this.context, this.path, tree, callback); + } + + create(type : ResourceType, callback : SimpleCallback) : void + create(type : ResourceType, createIntermediates : boolean, callback : SimpleCallback) : void + create(type : ResourceType, _createIntermediates : any, _callback ?: SimpleCallback) : void + { + this.fs.create(this.context, this.path, type, _createIntermediates, _callback); + } + etag(callback : ReturnCallback) : void + { + this.fs.etag(this.context, this.path, callback); + } + move(pathTo : Path, callback : ReturnCallback) : void + move(pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + move(pathTo : Path, _overwrite : any, _callback ?: ReturnCallback) : void + { + this.fs.move(this.context, this.path, pathTo, _overwrite, _callback); + } + rename(newName : string, callback : SimpleCallback) : void + { + this.fs.rename(this.context, this.path, newName, callback); + } + availableLocks(callback : ReturnCallback) : void + { + this.fs.availableLocks(this.context, this.path, callback); + } + lockManager(callback : ReturnCallback) : void + { + this.fs.lockManager(this.context, this.path, callback); + } + propertyManager(callback : ReturnCallback) : void + { + this.fs.propertyManager(this.context, this.path, callback); + } + readDir(callback : ReturnCallback) : void + readDir(retrieveExternalFiles : boolean, callback : ReturnCallback) : void + readDir(_retrieveExternalFiles : any, _callback ?: ReturnCallback) : void + { + this.fs.readDir(this.context, this.path, _retrieveExternalFiles, _callback); + } + creationDate(callback : ReturnCallback) : void + { + this.fs.creationDate(this.context, this.path, callback); + } + lastModifiedDate(callback : ReturnCallback) : void + { + this.fs.lastModifiedDate(this.context, this.path, callback); + } + webName(callback : ReturnCallback) : void + { + this.fs.webName(this.context, this.path, callback); + } + displayName(callback : ReturnCallback) : void + { + this.fs.displayName(this.context, this.path, callback); + } + type(callback : ReturnCallback) : void + { + this.fs.type(this.context, this.path, callback); + } + + listDeepLocks(callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(depth : number, callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(_depth : any, _callback ?: ReturnCallback<{ [path : string] : Lock[] }>) + { + this.fs.listDeepLocks(this.context, this.path, _depth, _callback); + } +} + +export interface ISerializableFileSystem +{ + serializer() : FileSystemSerializer; + serialize(callback : ReturnCallback) : void +} + +export type OpenWriteStreamMode = 'mustCreate' | 'canCreate' | 'mustExist' | 'canCreateIntermediates' | 'mustCreateIntermediates'; + +export abstract class FileSystem implements ISerializableFileSystem +{ + private __serializer; + + constructor(serializer : FileSystemSerializer) + { + this.__serializer = serializer; + } + + serializer() : FileSystemSerializer + { + return this.__serializer; + } + + contextualize(ctx : RequestContext) : ContextualFileSystem + { + return new ContextualFileSystem(this, ctx); + } + + resource(ctx : RequestContext, path : Path) : Resource + { + return new Resource(path, this, ctx); + } + + fastExistCheckEx(ctx : RequestContext, path : Path, errorCallback : SimpleCallback, callback : () => void) : void + { + if(!this._fastExistCheck) + return callback(); + + this._fastExistCheck(ctx, path, (exists) => { + if(!exists) + errorCallback(Errors.ResourceNotFound); + else + callback(); + }); + } + fastExistCheckExReverse(ctx : RequestContext, path : Path, errorCallback : SimpleCallback, callback : () => void) : void + { + if(!this._fastExistCheck) + return callback(); + + this._fastExistCheck(ctx, path, (exists) => { + if(exists) + errorCallback(Errors.ResourceAlreadyExists); + else + callback(); + }); + } + protected fastExistCheck(ctx : RequestContext, path : Path, callback : (exists : boolean) => void) : void + { + if(!this._fastExistCheck) + return callback(true); + + this._fastExistCheck(ctx, path, (exists) => callback(!!exists)); + } + protected _fastExistCheck?(ctx : RequestContext, path : Path, callback : (exists : boolean) => void) : void + + create(ctx : RequestContext, path : Path, type : ResourceType, callback : SimpleCallback) : void + create(ctx : RequestContext, path : Path, type : ResourceType, createIntermediates : boolean, callback : SimpleCallback) : void + create(ctx : RequestContext, path : Path, type : ResourceType, _createIntermediates : boolean | SimpleCallback, _callback ?: SimpleCallback) : void + { + const createIntermediates = _callback ? _createIntermediates as boolean : false; + const callback = _callback ? _callback : _createIntermediates as SimpleCallback; + + const go = () => { + this._create(path, { + context: ctx, + type + }, callback); + } + + this.fastExistCheckExReverse(ctx, path, callback, () => { + this.type(ctx, path.getParent(), (e, type) => { + if(e === Errors.ResourceNotFound) + { + if(!createIntermediates) + return callback(Errors.IntermediateResourceMissing); + + this.getFullPath(ctx, path, (e, fullPath) => { + if(e) + return callback(e); + + fullPath = fullPath.getParent(); + ctx.getResource(fullPath, (e, r) => { + if(e) + return callback(e); + + r.create(ResourceType.Directory, (e) => { + if(e && e !== Errors.ResourceAlreadyExists) + return callback(e); + + go(); + }) + }) + }) + return; + } + if(e) + return callback(e); + + if(!type.isDirectory) + return callback(Errors.WrongParentTypeForCreation); + + go(); + }) + }) + } + protected abstract _create(path : Path, ctx : CreateInfo, callback : SimpleCallback) : void + + etag(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._etag) + return this.lastModifiedDate(ctx, path, (e, date) => { + if(e) + return callback(e); + callback(null, '"' + crypto.createHash('md5').update(date.toString()).digest('hex') + '"'); + }) + + this._etag(path, { + context: ctx + }, callback); + }) + } + protected _etag?(path : Path, ctx : ETagInfo, callback : ReturnCallback) : void + + delete(ctx : RequestContext, path : Path, callback : SimpleCallback) : void + delete(ctx : RequestContext, path : Path, depth : number, callback : SimpleCallback) : void + delete(ctx : RequestContext, path : Path, _depth : number | SimpleCallback, _callback ?: SimpleCallback) : void + { + const depth = _callback ? _depth as number : -1; + const callback = _callback ? _callback : _depth as SimpleCallback; + + this.fastExistCheckEx(ctx, path, callback, () => { + this._delete(path, { + context: ctx, + depth + }, callback); + }) + } + protected abstract _delete(path : Path, ctx : DeleteInfo, callback : SimpleCallback) : void + + openWriteStream(ctx : RequestContext, path : Path, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, targetSource : boolean, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, mode : OpenWriteStreamMode, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, mode : OpenWriteStreamMode, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, mode : OpenWriteStreamMode, targetSource : boolean, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, mode : OpenWriteStreamMode, targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(ctx : RequestContext, path : Path, _mode : OpenWriteStreamMode | boolean | number | Return2Callback, _targetSource ?: boolean | number | Return2Callback, _estimatedSize ?: number | Return2Callback, _callback ?: Return2Callback) : void + { + let targetSource = true; + for(const obj of [ _mode, _targetSource ]) + if(obj && obj.constructor === Boolean) + targetSource = obj as boolean; + + let estimatedSize = -1; + for(const obj of [ _mode, _targetSource, _estimatedSize ]) + if(obj && obj.constructor === Number) + estimatedSize = obj as number; + + let callback; + for(const obj of [ _mode, _targetSource, _estimatedSize, _callback ]) + if(obj && obj.constructor === Function) + callback = obj as Return2Callback; + + const mode = _mode && _mode.constructor === String ? _mode as OpenWriteStreamMode : 'mustExist'; + let created = false; + + if(!this._openWriteStream) + return callback(Errors.InvalidOperation); + + const go = (callback : Return2Callback) => + { + this._openWriteStream(path, { + context: ctx, + estimatedSize, + targetSource, + mode + }, (e, wStream) => callback(e, wStream, created)); + } + + const createAndGo = (intermediates : boolean) => + { + this.create(ctx, path, ResourceType.File, intermediates, (e) => { + if(e) + return callback(e); + + created = true; + go(callback); + }) + } + + switch(mode) + { + case 'mustExist': + this.fastExistCheckEx(ctx, path, callback, () => go(callback)); + break; + + case 'mustCreateIntermediates': + case 'mustCreate': + createAndGo(mode === 'mustCreateIntermediates'); + break; + + case 'canCreateIntermediates': + case 'canCreate': + go((e, wStream) => { + if(e === Errors.ResourceNotFound) + createAndGo(mode === 'canCreateIntermediates'); + else + callback(e, wStream); + }) + break; + + default: + callback(Errors.IllegalArguments); + break; + } + } + protected _openWriteStream?(path : Path, ctx : OpenWriteStreamInfo, callback : ReturnCallback) : void + + openReadStream(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + openReadStream(ctx : RequestContext, path : Path, estimatedSize : number, callback : ReturnCallback) : void + openReadStream(ctx : RequestContext, path : Path, targetSource : boolean, callback : ReturnCallback) : void + openReadStream(ctx : RequestContext, path : Path, targetSource : boolean, estimatedSize : number, callback : ReturnCallback) : void + openReadStream(ctx : RequestContext, path : Path, _targetSource : boolean | number | ReturnCallback, _estimatedSize ?: number | ReturnCallback, _callback ?: ReturnCallback) : void + { + const targetSource = _targetSource.constructor === Boolean ? _targetSource as boolean : true; + const estimatedSize = _callback ? _estimatedSize as number : _estimatedSize ? _targetSource as number : -1; + const callback = _callback ? _callback : _estimatedSize ? _estimatedSize as ReturnCallback : _targetSource as ReturnCallback; + + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._openReadStream) + return callback(Errors.InvalidOperation); + + this._openReadStream(path, { + context: ctx, + estimatedSize, + targetSource + }, callback); + }) + } + protected _openReadStream?(path : Path, ctx : OpenReadStreamInfo, callback : ReturnCallback) : void + + move(ctx : RequestContext, pathFrom : Path, pathTo : Path, callback : ReturnCallback) : void + move(ctx : RequestContext, pathFrom : Path, pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + move(ctx : RequestContext, pathFrom : Path, pathTo : Path, _overwrite : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const callback = _callback ? _callback : _overwrite as ReturnCallback; + const overwrite = _callback ? _overwrite as boolean : false; + + const go = () => + { + if(this._move) + { + this._move(pathFrom, pathTo, { + context: ctx, + overwrite + }, callback); + return; + } + + StandardMethods.standardMove(ctx, pathFrom, this, pathTo, this, callback); + } + + this.fastExistCheckEx(ctx, pathFrom, callback, () => { + if(!overwrite) + this.fastExistCheckExReverse(ctx, pathTo, callback, go); + else + go(); + }) + } + protected _move?(pathFrom : Path, pathTo : Path, ctx : MoveInfo, callback : ReturnCallback) : void + + copy(ctx : RequestContext, pathFrom : Path, pathTo : Path, callback : ReturnCallback) : void + copy(ctx : RequestContext, pathFrom : Path, pathTo : Path, depth : number, callback : ReturnCallback) : void + copy(ctx : RequestContext, pathFrom : Path, pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + copy(ctx : RequestContext, pathFrom : Path, pathTo : Path, overwrite : boolean, depth : number, callback : ReturnCallback) : void + copy(ctx : RequestContext, pathFrom : Path, pathTo : Path, _overwrite : boolean | number | ReturnCallback, _depth ?: number | ReturnCallback, _callback ?: ReturnCallback) : void + { + const overwrite = _overwrite.constructor === Boolean ? _overwrite as boolean : false; + const depth = _callback ? _depth as number : !_depth ? -1 : _overwrite.constructor === Number ? _overwrite as number : -1; + const callback = _callback ? _callback : _depth ? _depth as ReturnCallback : _overwrite as ReturnCallback; + + if(this._copy) + { + const go = () => + { + this._copy(pathFrom, pathTo, { + context: ctx, + depth, + overwrite + }, callback); + } + + this.fastExistCheckEx(ctx, pathFrom, callback, () => { + if(!overwrite) + this.fastExistCheckExReverse(ctx, pathTo, callback, go); + else + go(); + }) + } + else + StandardMethods.standardCopy(ctx, pathFrom, this, pathTo, this, overwrite, depth, callback); + + } + protected _copy?(pathFrom : Path, pathTo : Path, ctx : CopyInfo, callback : ReturnCallback) : void + + rename(ctx : RequestContext, pathFrom : Path, newName : string, callback : SimpleCallback) : void + { + this.fastExistCheckEx(ctx, pathFrom, callback, () => { + this.fastExistCheckExReverse(ctx, pathFrom.getParent().getChildPath(newName), callback, () => { + if(this._rename) + { + this._rename(pathFrom, newName, { + context: ctx + }, callback); + return; + } + + this.move(ctx, pathFrom, pathFrom.getParent().getChildPath(newName), callback); + }) + }) + } + protected _rename?(pathFrom : Path, newName : string, ctx : RenameInfo, callback : SimpleCallback) : void + + mimeType(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + mimeType(ctx : RequestContext, path : Path, targetSource : boolean, callback : ReturnCallback) : void + mimeType(ctx : RequestContext, path : Path, _targetSource : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const targetSource = _callback ? _targetSource as boolean : true; + const callback = _callback ? _callback : _targetSource as ReturnCallback; + + this.fastExistCheckEx(ctx, path, callback, () => { + if(this._mimeType) + { + this._mimeType(path, { + context: ctx, + targetSource + }, callback); + return; + } + + StandardMethods.standardMimeType(ctx, this, path, targetSource, callback); + }) + } + protected _mimeType?(path : Path, ctx : MimeTypeInfo, callback : ReturnCallback) : void + + size(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + size(ctx : RequestContext, path : Path, targetSource : boolean, callback : ReturnCallback) : void + size(ctx : RequestContext, path : Path, _targetSource : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const targetSource = _callback ? _targetSource as boolean : true; + const callback = _callback ? _callback : _targetSource as ReturnCallback; + + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._size) + return callback(Errors.InvalidOperation); + + this._size(path, { + context: ctx, + targetSource + }, callback); + }) + } + protected _size?(path : Path, ctx : SizeInfo, callback : ReturnCallback) : void + + availableLocks(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._availableLocks) + return callback(null, [ + new LockKind(LockScope.Exclusive, LockType.Write), + new LockKind(LockScope.Shared, LockType.Write) + ]); + + this._availableLocks(path, { + context: ctx + }, callback); + }) + } + protected _availableLocks?(path : Path, ctx : AvailableLocksInfo, callback : ReturnCallback) : void + + lockManager(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + this._lockManager(path, { + context: ctx + }, callback); + }) + } + protected abstract _lockManager(path : Path, ctx : LockManagerInfo, callback : ReturnCallback) : void + + propertyManager(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + this._propertyManager(path, { + context: ctx + }, callback); + }) + } + protected abstract _propertyManager(path : Path, ctx : PropertyManagerInfo, callback : ReturnCallback) : void + + readDir(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + readDir(ctx : RequestContext, path : Path, retrieveExternalFiles : boolean, callback : ReturnCallback) : void + readDir(ctx : RequestContext, path : Path, _retrieveExternalFiles : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const retrieveExternalFiles = _callback ? _retrieveExternalFiles as boolean : false; + const callback = _callback ? _callback : _retrieveExternalFiles as ReturnCallback; + + this.fastExistCheckEx(ctx, path, callback, () => { + const next = (base : string[]) => { + if(!this._readDir) + return callback(null, base); + + this._readDir(path, { + context: ctx + }, (e, paths) => { + if(e) + return callback(e); + + if(paths.length === 0) + return callback(null, base); + + if(paths[0].constructor === String) + base = base.concat(paths as string[]); + else + base = base.concat((paths as Path[]).map((p) => p.fileName())); + + callback(null, base); + }); + } + + if(!retrieveExternalFiles) + return next([]); + + this.getFullPath(ctx, (e, thisFullPath) => { + if(e) + return callback(e); + + ctx.server.getChildFileSystems(thisFullPath.getChildPath(path), (fss) => { + next(fss.map((f) => f.path.fileName())); + }) + }) + }) + } + protected _readDir?(path : Path, ctx : ReadDirInfo, callback : ReturnCallback) : void + + creationDate(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + this._creationDate(path, { + context: ctx + }, callback); + }) + } + protected abstract _creationDate(path : Path, ctx : CreationDateInfo, callback : ReturnCallback) : void + + lastModifiedDate(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + this._lastModifiedDate(path, { + context: ctx + }, callback); + }) + } + protected abstract _lastModifiedDate(path : Path, ctx : LastModifiedDateInfo, callback : ReturnCallback) : void + + webName(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + if(path.isRoot()) + this.getFullPath(ctx, (e, path) => callback(e, e ? null : path.fileName())); + else + callback(null, path.fileName()); + }) + } + + displayName(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + if(!this._displayName) + return this.webName(ctx, path, callback); + + this._displayName(path, { + context: ctx + }, callback); + }) + } + protected _displayName?(path : Path, ctx : DisplayNameInfo, callback : ReturnCallback) : void + + type(ctx : RequestContext, path : Path, callback : ReturnCallback) : void + { + this.fastExistCheckEx(ctx, path, callback, () => { + this._type(path, { + context: ctx + }, callback); + }) + } + protected abstract _type(path : Path, ctx : TypeInfo, callback : ReturnCallback) : void + + addSubTree(ctx : RequestContext, subTree : SubTree, callback : SimpleCallback) + addSubTree(ctx : RequestContext, resourceType : ResourceType, callback : SimpleCallback) + addSubTree(ctx : RequestContext, rootPath : Path, subTree : SubTree, callback : SimpleCallback) + addSubTree(ctx : RequestContext, rootPath : Path, resourceType : ResourceType, callback : SimpleCallback) + addSubTree(ctx : RequestContext, _rootPath : Path | SubTree | ResourceType | SimpleCallback, _tree : SubTree | ResourceType | SimpleCallback, _callback ?: SimpleCallback) + { + const callback = _callback ? _callback : _tree as SimpleCallback; + const tree = _callback ? _tree as SubTree | ResourceType : _rootPath as SubTree | ResourceType; + const rootPath = _callback ? _rootPath as Path : new Path('/'); + + if(tree.constructor === ResourceType) + { + this.create(ctx, rootPath, tree as ResourceType, callback); + } + else + { + new Workflow() + .each(Object.keys(tree), (name, cb) => { + const value = tree[name]; + if(value.constructor === ResourceType) + this.addSubTree(ctx, rootPath.getChildPath(name), value, cb) + else + this.addSubTree(ctx, rootPath.getChildPath(name), ResourceType.Directory, (e) => { + if(e) + return cb(e); + + this.addSubTree(ctx, rootPath.getChildPath(name), value, cb); + }) + }) + .error(callback) + .done(() => callback()); + } + } + + listDeepLocks(ctx : RequestContext, startPath : Path, callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(ctx : RequestContext, startPath : Path, depth : number, callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(ctx : RequestContext, startPath : Path, _depth : number | ReturnCallback<{ [path : string] : Lock[] }>, _callback ?: ReturnCallback<{ [path : string] : Lock[] }>) + { + const depth = _callback ? _depth as number : 0; + const callback = _callback ? _callback : _depth as ReturnCallback<{ [path : string] : Lock[] }>; + + this.lockManager(ctx, startPath, (e, lm) => { + if(e) + return callback(e); + + lm.getLocks((e, locks) => { + if(e) + return callback(e); + + if(depth != -1) + locks = locks.filter((f) => f.depth === -1 || f.depth >= depth); + + const go = (fs : FileSystem, parentPath : Path) => + { + const destDepth = depth === -1 ? -1 : depth + 1; + fs.listDeepLocks(ctx, parentPath, destDepth, (e, pLocks) => { + if(e) + return callback(e); + + if(locks && locks.length > 0) + pLocks[startPath.toString()] = locks; + callback(null, pLocks); + }) + } + + if(!startPath.isRoot()) + return go(this, startPath.getParent()); + + this.getFullPath(ctx, (e, fsPath) => { + if(e) + return callback(e); + + if(fsPath.isRoot()) + { + const result = {}; + if(locks && locks.length > 0) + result[startPath.toString()] = locks; + return callback(null, result); + } + + ctx.server.getFileSystem(fsPath.getParent(), (fs, _, subPath) => { + go(fs, subPath); + }) + }) + }) + }) + } + + getFullPath(ctx : RequestContext, callback : ReturnCallback) + getFullPath(ctx : RequestContext, path : Path, callback : ReturnCallback) + getFullPath(ctx : RequestContext, _path : Path | ReturnCallback, _callback ?: ReturnCallback) + { + const path = _callback ? _path as Path : undefined; + const callback = _callback ? _callback : _path as ReturnCallback; + + ctx.server.getFileSystemPath(this, (fsPath) => { + callback(null, path ? fsPath.getChildPath(path) : fsPath); + }) + } + + serialize(callback : ReturnCallback) : void + { + this.serializer().serialize(this, callback); + } +} + +export interface SubTree +{ + [name : string] : ResourceType | SubTree +} + +export interface FileSystemSerializer +{ + uid() : string; + serialize(fs : FileSystem, callback : ReturnCallback) : void; + unserialize(serializedData : any, callback : ReturnCallback) : void; +} + +export interface SerializedData +{ + [path : string] : { + serializer : string + data : any + } +} +export interface UnserializedData +{ + [path : string] : FileSystem +} +export function serialize(fileSystems : UnserializedData, callback : ReturnCallback) +{ + const result : SerializedData = {}; + new Workflow() + .each(Object.keys(fileSystems), (path, cb) => { + const fs = fileSystems[path]; + const serializer = fs.serializer(); + serializer.serialize(fs, (e, data) => { + if(!e) + result[path] = { + serializer: serializer.uid(), + data + }; + cb(e) + }); + }) + .error(callback) + .done(() => callback(null, result)); +} + +export function unserialize(serializedData : SerializedData, serializers : FileSystemSerializer[], callback : ReturnCallback) +{ + const result : UnserializedData = {}; + new Workflow() + .each(Object.keys(serializedData), (path, cb) => { + const sd = serializedData[path]; + let serializer : FileSystemSerializer = null; + for(const s of serializers) + if(s.uid() === sd.serializer) + { + serializer = s; + break; + } + + if(!serializer) + return cb(Errors.SerializerNotFound) + + serializer.unserialize(sd.data, (e, fs) => { + if(!e) + result[path] = fs; + callback(e); + }) + }) + .error(callback) + .done(() => callback(null, result)); +} diff --git a/src/manager/v2/fileSystem/LockManager.ts b/src/manager/v2/fileSystem/LockManager.ts new file mode 100644 index 00000000..bcb20b8f --- /dev/null +++ b/src/manager/v2/fileSystem/LockManager.ts @@ -0,0 +1,73 @@ +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { XMLElement } from '../../../helper/XML' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockType } from '../../../resource/lock/LockType' +import { LockKind } from '../../../resource/lock/LockKind' +import { Workflow } from '../../../helper/Workflow' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { Path } from '../Path' +import { ReturnCallback, SimpleCallback } from './CommonTypes' +import * as mimeTypes from 'mime-types' +import * as crypto from 'crypto' + +export interface ILockManager +{ + getLocks(callback : ReturnCallback) : void + setLock(lock : Lock, callback : SimpleCallback) : void + removeLock(uuid : string, callback : ReturnCallback) : void + getLock(uuid : string, callback : ReturnCallback) : void + refresh(uuid : string, timeout : number, callback : ReturnCallback) : void +} +export class LocalLockManager implements ILockManager +{ + locks : Lock[] = []; + + getLocks(callback : ReturnCallback) : void + { + this.locks = this.locks.filter((lock) => !lock.expired()); + + callback(null, this.locks); + } + + setLock(lock : Lock, callback : SimpleCallback) : void + { + this.locks.push(lock); + callback(null); + } + + removeLock(uuid : string, callback : ReturnCallback) : void + { + for(let index = 0; index < this.locks.length; ++index) + if(this.locks[index].uuid === uuid) + { + this.locks.splice(index, 1); + return callback(null, true); + } + + callback(null, false); + } + + getLock(uuid : string, callback : ReturnCallback) : void + { + this.locks = this.locks.filter((lock) => !lock.expired()); + + for(const lock of this.locks) + if(lock.uuid === uuid) + return callback(null, lock); + + callback(); + } + + refresh(uuid : string, timeout : number, callback : ReturnCallback) : void + { + this.getLock(uuid, (e, lock) => { + if(e || !lock) + return callback(e); + + lock.refresh(timeout); + callback(null, lock); + }) + } +} diff --git a/src/manager/v2/fileSystem/PropertyManager.ts b/src/manager/v2/fileSystem/PropertyManager.ts new file mode 100644 index 00000000..9de1984d --- /dev/null +++ b/src/manager/v2/fileSystem/PropertyManager.ts @@ -0,0 +1,54 @@ +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { XMLElement } from '../../../helper/XML' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockType } from '../../../resource/lock/LockType' +import { LockKind } from '../../../resource/lock/LockKind' +import { Workflow } from '../../../helper/Workflow' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { Path } from '../Path' +import { ReturnCallback, SimpleCallback, ResourcePropertyValue } from './CommonTypes' +import * as mimeTypes from 'mime-types' +import * as crypto from 'crypto' + +export interface PropertyBag +{ + [name : string] : ResourcePropertyValue +} +export interface IPropertyManager +{ + setProperty(name : string, value : ResourcePropertyValue, callback : SimpleCallback) : void + getProperty(name : string, callback : ReturnCallback) : void + removeProperty(name : string, callback : SimpleCallback) : void + getProperties(callback : ReturnCallback, byCopy ?: boolean) : void +} +export class LocalPropertyManager implements IPropertyManager +{ + properties : { + [name : string] : ResourcePropertyValue + } = { }; + + setProperty(name : string, value : ResourcePropertyValue, callback : SimpleCallback) : void + { + this.properties[name] = value; + callback(null); + } + + getProperty(name : string, callback : ReturnCallback) : void + { + const property = this.properties[name]; + callback(property ? null : Errors.PropertyNotFound, property); + } + + removeProperty(name : string, callback : SimpleCallback) : void + { + delete this.properties[name]; + callback(null); + } + + getProperties(callback : ReturnCallback, byCopy : boolean = false) : void + { + callback(null, byCopy ? this.properties : JSON.parse(JSON.stringify(this.properties))); + } +} diff --git a/src/manager/v2/fileSystem/Resource.ts b/src/manager/v2/fileSystem/Resource.ts new file mode 100644 index 00000000..2ea0a0a2 --- /dev/null +++ b/src/manager/v2/fileSystem/Resource.ts @@ -0,0 +1,151 @@ +import { AvailableLocksInfo, CopyInfo, CreateInfo, CreationDateInfo, DeleteInfo, DisplayNameInfo, ETagInfo, IContextInfo, LastModifiedDateInfo, LockManagerInfo, MimeTypeInfo, MoveInfo, OpenReadStreamInfo, OpenWriteStreamInfo, PropertyManagerInfo, ReadDirInfo, RenameInfo, SizeInfo, TypeInfo, WebNameInfo } from './ContextInfo' +import { ReturnCallback, SimpleCallback, Return2Callback, OpenWriteStreamMode, SubTree, ResourceType } from './CommonTypes' +import { FileSystemSerializer, ISerializableFileSystem } from './Serialization' +import { FileSystem } from './FileSystem' +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { XMLElement } from '../../../helper/XML' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockType } from '../../../resource/lock/LockType' +import { LockKind } from '../../../resource/lock/LockKind' +import { Workflow } from '../../../helper/Workflow' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { Path } from '../Path' +import { IPropertyManager } from './PropertyManager' +import { ILockManager } from './LockManager' +import * as mimeTypes from 'mime-types' +import * as crypto from 'crypto' + +export class Resource +{ + constructor(public path : Path, public fs : FileSystem, public context : RequestContext) + { } + + delete(callback : SimpleCallback) : void + delete(depth : number, callback : SimpleCallback) : void + delete(_depth : any, _callback ?: SimpleCallback) : void + { + this.fs.delete(this.context, this.path, _depth, _callback); + } + + openWriteStream(callback : Return2Callback) : void + openWriteStream(estimatedSize : number, callback : Return2Callback) : void + openWriteStream(targetSource : boolean, callback : Return2Callback) : void + openWriteStream(targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(mode : OpenWriteStreamMode, callback : Return2Callback) : void + openWriteStream(mode : OpenWriteStreamMode, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(mode : OpenWriteStreamMode, targetSource : boolean, callback : Return2Callback) : void + openWriteStream(mode : OpenWriteStreamMode, targetSource : boolean, estimatedSize : number, callback : Return2Callback) : void + openWriteStream(_mode : any, _targetSource ?: any, _estimatedSize ?: any, _callback ?: Return2Callback) : void + { + this.fs.openWriteStream(this.context, this.path, _mode, _targetSource, _estimatedSize, _callback); + } + + openReadStream(callback : ReturnCallback) : void + openReadStream(estimatedSize : number, callback : ReturnCallback) : void + openReadStream(targetSource : boolean, callback : ReturnCallback) : void + openReadStream(targetSource : boolean, estimatedSize : number, callback : ReturnCallback) : void + openReadStream(_targetSource : any, _estimatedSize ?: any, _callback ?: ReturnCallback) : void + { + this.fs.openReadStream(this.context, this.path, _targetSource, _estimatedSize, _callback); + } + + copy(pathTo : Path, callback : ReturnCallback) : void + copy(pathTo : Path, depth : number, callback : ReturnCallback) : void + copy(pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + copy(pathTo : Path, overwrite : boolean, depth : number, callback : ReturnCallback) : void + copy(pathTo : Path, _overwrite : any, _depth ?: any, _callback ?: ReturnCallback) : void + { + this.fs.copy(this.context, this.path, pathTo, _overwrite, _depth, _callback); + } + + mimeType(callback : ReturnCallback) : void + mimeType(targetSource : boolean, callback : ReturnCallback) : void + mimeType(_targetSource : any, _callback ?: ReturnCallback) : void + { + this.fs.mimeType(this.context, this.path, _targetSource, _callback); + } + + size(callback : ReturnCallback) : void + size(targetSource : boolean, callback : ReturnCallback) : void + size(_targetSource : any, _callback ?: ReturnCallback) : void + { + this.fs.size(this.context, this.path, _targetSource, _callback); + } + + addSubTree(subTree : SubTree, callback : SimpleCallback) + addSubTree(resourceType : ResourceType, callback : SimpleCallback) + addSubTree(tree : any, callback : SimpleCallback) + { + this.fs.addSubTree(this.context, this.path, tree, callback); + } + + create(type : ResourceType, callback : SimpleCallback) : void + create(type : ResourceType, createIntermediates : boolean, callback : SimpleCallback) : void + create(type : ResourceType, _createIntermediates : any, _callback ?: SimpleCallback) : void + { + this.fs.create(this.context, this.path, type, _createIntermediates, _callback); + } + etag(callback : ReturnCallback) : void + { + this.fs.etag(this.context, this.path, callback); + } + move(pathTo : Path, callback : ReturnCallback) : void + move(pathTo : Path, overwrite : boolean, callback : ReturnCallback) : void + move(pathTo : Path, _overwrite : any, _callback ?: ReturnCallback) : void + { + this.fs.move(this.context, this.path, pathTo, _overwrite, _callback); + } + rename(newName : string, callback : ReturnCallback) : void + rename(newName : string, overwrite : boolean, callback : ReturnCallback) : void + rename(newName : string, _overwrite : any, _callback ?: ReturnCallback) : void + { + this.fs.rename(this.context, this.path, newName, _overwrite, _callback); + } + availableLocks(callback : ReturnCallback) : void + { + this.fs.availableLocks(this.context, this.path, callback); + } + lockManager(callback : ReturnCallback) : void + { + this.fs.lockManager(this.context, this.path, callback); + } + propertyManager(callback : ReturnCallback) : void + { + this.fs.propertyManager(this.context, this.path, callback); + } + readDir(callback : ReturnCallback) : void + readDir(retrieveExternalFiles : boolean, callback : ReturnCallback) : void + readDir(_retrieveExternalFiles : any, _callback ?: ReturnCallback) : void + { + this.fs.readDir(this.context, this.path, _retrieveExternalFiles, _callback); + } + creationDate(callback : ReturnCallback) : void + { + this.fs.creationDate(this.context, this.path, callback); + } + lastModifiedDate(callback : ReturnCallback) : void + { + this.fs.lastModifiedDate(this.context, this.path, callback); + } + webName(callback : ReturnCallback) : void + { + this.fs.webName(this.context, this.path, callback); + } + displayName(callback : ReturnCallback) : void + { + this.fs.displayName(this.context, this.path, callback); + } + type(callback : ReturnCallback) : void + { + this.fs.type(this.context, this.path, callback); + } + + listDeepLocks(callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(depth : number, callback : ReturnCallback<{ [path : string] : Lock[] }>) + listDeepLocks(_depth : any, _callback ?: ReturnCallback<{ [path : string] : Lock[] }>) + { + this.fs.listDeepLocks(this.context, this.path, _depth, _callback); + } +} diff --git a/src/manager/v2/fileSystem/Serialization.ts b/src/manager/v2/fileSystem/Serialization.ts new file mode 100644 index 00000000..e978b185 --- /dev/null +++ b/src/manager/v2/fileSystem/Serialization.ts @@ -0,0 +1,85 @@ +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { XMLElement } from '../../../helper/XML' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockType } from '../../../resource/lock/LockType' +import { LockKind } from '../../../resource/lock/LockKind' +import { Workflow } from '../../../helper/Workflow' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { Path } from '../Path' +import { ReturnCallback, SimpleCallback } from './CommonTypes' +import { FileSystem } from './FileSystem' +import * as mimeTypes from 'mime-types' +import * as crypto from 'crypto' + +export interface ISerializableFileSystem +{ + serializer() : FileSystemSerializer; + serialize(callback : ReturnCallback) : void +} + +export interface FileSystemSerializer +{ + uid() : string; + serialize(fs : FileSystem, callback : ReturnCallback) : void; + unserialize(serializedData : any, callback : ReturnCallback) : void; +} + +export interface SerializedData +{ + [path : string] : { + serializer : string + data : any + } +} +export interface UnserializedData +{ + [path : string] : FileSystem +} +export function serialize(fileSystems : UnserializedData, callback : ReturnCallback) +{ + const result : SerializedData = {}; + new Workflow() + .each(Object.keys(fileSystems), (path, cb) => { + const fs = fileSystems[path]; + const serializer = fs.serializer(); + serializer.serialize(fs, (e, data) => { + if(!e) + result[path] = { + serializer: serializer.uid(), + data + }; + cb(e) + }); + }) + .error(callback) + .done(() => callback(null, result)); +} + +export function unserialize(serializedData : SerializedData, serializers : FileSystemSerializer[], callback : ReturnCallback) +{ + const result : UnserializedData = {}; + new Workflow() + .each(Object.keys(serializedData), (path, cb) => { + const sd = serializedData[path]; + let serializer : FileSystemSerializer = null; + for(const s of serializers) + if(s.uid() === sd.serializer) + { + serializer = s; + break; + } + + if(!serializer) + return cb(Errors.SerializerNotFound) + + serializer.unserialize(sd.data, (e, fs) => { + if(!e) + result[path] = fs; + callback(e); + }) + }) + .error(callback) + .done(() => callback(null, result)); +} diff --git a/src/manager/v2/fileSystem/StandardMethods.ts b/src/manager/v2/fileSystem/StandardMethods.ts new file mode 100644 index 00000000..805533d1 --- /dev/null +++ b/src/manager/v2/fileSystem/StandardMethods.ts @@ -0,0 +1,263 @@ +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { XMLElement } from '../../../helper/XML' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockType } from '../../../resource/lock/LockType' +import { LockKind } from '../../../resource/lock/LockKind' +import { Workflow } from '../../../helper/Workflow' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { Path } from '../Path' +import { ReturnCallback, SimpleCallback } from './CommonTypes' +import { FileSystem } from './FileSystem' +import * as mimeTypes from 'mime-types' +import * as crypto from 'crypto' + +export abstract class StandardMethods +{ + public static standardMove(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, callback : ReturnCallback) : void + public static standardMove(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, overwrite : boolean, callback : ReturnCallback) : void + public static standardMove(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, _overwrite : boolean | ReturnCallback, _callback ?: ReturnCallback) : void + { + const callback = _callback ? _callback : _overwrite as ReturnCallback; + const overwrite = _callback ? _overwrite as boolean : false; + + const go = (fullPathFrom ?: Path) => + { + StandardMethods.standardCopy(ctx, subPathFrom, fsFrom, subPathTo, fsTo, overwrite, -1, (e, overwritten) => { + if(e) + return callback(e, overwritten); + + if(fullPathFrom) + { // subPathFrom.isRoot() === true + ctx.server.removeFileSystem(fullPathFrom, (nb) => { + callback(null, overwritten); + }) + return; + } + + fsFrom.delete(ctx, subPathFrom, -1, (e) => callback(e, overwritten)); + }) + } + + if(subPathFrom.isRoot()) + { + fsFrom.getFullPath(ctx, (e, fullPathFrom) => { + go(fullPathFrom); + }) + } + else + go(); + } + + + + public static standardCopy(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, callback : ReturnCallback) : void + public static standardCopy(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, depth : number, callback : ReturnCallback) : void + public static standardCopy(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, overwrite : boolean, callback : ReturnCallback) : void + public static standardCopy(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, overwrite : boolean, depth : number, callback : ReturnCallback) : void + public static standardCopy(ctx : RequestContext, subPathFrom : Path, fsFrom : FileSystem, subPathTo : Path, fsTo : FileSystem, _overwrite : boolean | number | ReturnCallback, _depth ?: number | ReturnCallback, _callback ?: ReturnCallback) : void + { + const overwrite = _overwrite.constructor === Boolean ? _overwrite as boolean : false; + const depth = _callback ? _depth as number : !_depth ? -1 : _overwrite.constructor === Number ? _overwrite as number : -1; + const callback = _callback ? _callback : _depth ? _depth as ReturnCallback : _overwrite as ReturnCallback; + + if(subPathFrom.isRoot()) + { + fsTo.getFullPath(ctx, subPathTo, (e, fullPathTo) => { + if(e) + return callback(e); + + let overwritten = false; + ctx.server.getResource(ctx, fullPathTo, (e, r) => { + if(e) + return callback(e); + + r.type((e, type) => { + if(!e) + overwritten = true; + + ctx.server.setFileSystem(fullPathTo, fsFrom, (success) => { + callback(null, overwritten); + }) + }) + }) + }) + + return; + } + + const go = () => + { + const copyProperties = (callback : SimpleCallback) => + { + fsFrom.propertyManager(ctx, subPathFrom, (e, pmFrom) => { + if(e) + return callback(e); + + fsTo.propertyManager(ctx, subPathTo, (e, pmTo) => { + if(e) + return callback(e); + + pmFrom.getProperties((e, props) => { + if(e) + return callback(e); + + new Workflow() + .each(Object.keys(props), (name, cb) => pmTo.setProperty(name, props[name], cb)) + .error(callback) + .done(() => callback()) + }) + }) + }) + } + + const reverse1 = (e : Error) => { + fsTo.delete(ctx, subPathTo, () => callback(e)); + }; + + const copyContent = (callback : SimpleCallback) => + { + fsFrom.openReadStream(ctx, subPathFrom, (e, rStream) => { + if(e) + return reverse1(e); + + fsTo.openWriteStream(ctx, subPathTo, (e, wStream) => { + if(e) + return reverse1(e); + + let _callback = (e) => + { + _callback = () => {}; + callback(e); + } + + rStream.pipe(wStream); + rStream.on('error', _callback) + wStream.on('error', _callback) + wStream.on('finish', () => { + _callback(null); + }) + }) + }) + } + + const copyChildren = (callback : SimpleCallback) => + { + fsFrom.readDir(ctx, subPathFrom, false, (e, files) => { + if(e) + callback(e); + + const subDepth = depth === -1 ? -1 : Math.max(0, depth - 1); + + new Workflow() + .each(files, (file, cb) => StandardMethods.standardCopy(ctx, subPathFrom.getChildPath(file), fsFrom, subPathTo.getChildPath(file), fsTo, overwrite, subDepth, (e) => cb(e))) + .error(callback) + .done(() => callback()); + }) + } + + fsFrom.type(ctx, subPathFrom, (e, type) => { + if(e) + return callback(e); + + let overwritten = false; + + const startCopy = () => + { + const fns = [ copyProperties ]; + + if(type.isDirectory && depth !== 0) + fns.push(copyChildren); + if(type.isFile) + fns.push(copyContent); + + new Workflow() + .each(fns, (fn, cb) => fn(cb)) + .error((e) => callback(e, overwritten)) + .done(() => callback(null, overwritten)); + } + + fsTo.create(ctx, subPathTo, type, (e) => { + if(e === Errors.ResourceAlreadyExists && overwrite) + { + fsTo.delete(ctx, subPathTo, -1, (e) => { + if(e) + return callback(e); + overwritten = true; + + fsTo.create(ctx, subPathTo, type, (e) => { + if(e) + return callback(e); + startCopy(); + }) + }) + return; + } + else if(e) + return callback(e); + + startCopy(); + }) + }) + } + + fsFrom.fastExistCheckEx(ctx, subPathFrom, callback, () => { + if(!overwrite) + fsTo.fastExistCheckExReverse(ctx, subPathTo, callback, go); + else + go(); + }) + } + + public static standardMimeType(ctx : RequestContext, fs : FileSystem, path : Path, targetSource : boolean, callback : ReturnCallback) + public static standardMimeType(ctx : RequestContext, fs : FileSystem, path : Path, targetSource : boolean, useWebName : boolean, callback : ReturnCallback) + public static standardMimeType(ctx : RequestContext, fs : FileSystem, path : Path, targetSource : boolean, defaultMimeType : string, callback : ReturnCallback) + public static standardMimeType(ctx : RequestContext, fs : FileSystem, path : Path, targetSource : boolean, defaultMimeType : string, useWebName : boolean, callback : ReturnCallback) + public static standardMimeType(ctx : RequestContext, fs : FileSystem, path : Path, targetSource : boolean, _defaultMimeType : boolean | string | ReturnCallback, _useWebName ?: boolean | ReturnCallback, _callback ?: ReturnCallback) + { + let callback; + let useWebName = false; + let defaultMimeType = 'application/octet-stream'; + + if(_defaultMimeType.constructor === Function) + { + callback = _defaultMimeType as ReturnCallback; + } + else if(_defaultMimeType.constructor === Boolean) + { + callback = _useWebName as ReturnCallback; + if(_defaultMimeType !== undefined && _defaultMimeType !== null) + useWebName = _defaultMimeType as boolean; + } + else + { + callback = _callback as ReturnCallback; + if(_useWebName !== undefined && _useWebName !== null) + useWebName = _useWebName as boolean; + if(_defaultMimeType !== undefined && _defaultMimeType !== null) + defaultMimeType = _defaultMimeType as string; + } + + fs.type(ctx, path, (e, type) => { + if(e) + return callback(e, null); + + if(type.isFile) + { + const fn = useWebName ? fs.webName : fs.displayName; + fn.bind(fs)(ctx, path, (e, name) => { + if(e) + callback(e, null); + else + { + const mt = mimeTypes.contentType(name); + callback(null, mt ? mt as string : defaultMimeType); + } + }) + } + else + callback(Errors.NoMimeTypeForAFolder, null); + }) + } +} diff --git a/src/manager/v2/fileSystem/export.ts b/src/manager/v2/fileSystem/export.ts new file mode 100644 index 00000000..64a5f62a --- /dev/null +++ b/src/manager/v2/fileSystem/export.ts @@ -0,0 +1,10 @@ + +export * from './FileSystem' +export * from './CommonTypes' +export * from './ContextualFileSystem' +export * from './LockManager' +export * from './PropertyManager' +export * from './Resource' +export * from './Serialization' +export * from './StandardMethods' +export * from './ContextInfo' diff --git a/src/manager/v2/instances/FTPFileSystem.ts b/src/manager/v2/instances/FTPFileSystem.ts new file mode 100644 index 00000000..9db2c109 --- /dev/null +++ b/src/manager/v2/instances/FTPFileSystem.ts @@ -0,0 +1,357 @@ +import { + LocalPropertyManager, + LastModifiedDateInfo, + FileSystemSerializer, + OpenWriteStreamInfo, + PropertyManagerInfo, + OpenReadStreamInfo, + IPropertyManager, + LocalLockManager, + CreationDateInfo, + LockManagerInfo, + SimpleCallback, + ReturnCallback, + ResourceType, + ILockManager, + ReadDirInfo, + CreateInfo, + DeleteInfo, + FileSystem, + SizeInfo, + MoveInfo, + TypeInfo, +} from '../fileSystem/export' +import { Readable, Writable } from 'stream' +import { Errors } from '../../../Errors' +import { Path } from '../Path' +import { Transform } from 'stream' +import * as Client from 'ftp' + +export class _FTPFileSystemResource +{ + props : LocalPropertyManager + locks : LocalLockManager + type : ResourceType + + constructor(data ?: _FTPFileSystemResource) + { + if(!data) + { + this.props = new LocalPropertyManager(); + this.locks = new LocalLockManager(); + } + else + { + const rs = data as _FTPFileSystemResource; + this.props = rs.props; + this.locks = rs.locks; + } + } +} + +export class FTPSerializer implements FileSystemSerializer +{ + uid() : string + { + return 'FTPFSSerializer_1.0.0'; + } + + serialize(fs : FTPFileSystem, callback : ReturnCallback) : void + { + callback(null, { + resources: fs.resources, + config: fs.config + }); + } + + unserialize(serializedData : any, callback : ReturnCallback) : void + { + const fs = new FTPFileSystem(serializedData.config); + fs.resources = serializedData.resources; + callback(null, fs); + } +} + +export class FTPFileSystem extends FileSystem +{ + resources : { + [path : string] : _FTPFileSystemResource + } + + constructor(public config : Client.Options) + { + super(new FTPSerializer()); + + this.resources = { + '/': new _FTPFileSystemResource() + }; + } + + protected getRealPath(path : Path) + { + const sPath = path.toString(); + + return { + realPath: sPath, + resource: this.resources[sPath] + }; + } + + protected connect(callback : (client : Client) => void) + { + const client = new Client(); + client.on('ready', () => callback(client)); + client.connect(this.config); + } + + protected _create(path : Path, ctx : CreateInfo, _callback : SimpleCallback) : void + { + if(path.isRoot()) + return _callback(Errors.InvalidOperation); + + const { realPath } = this.getRealPath(path); + + this.connect((c) => { + const callback = (e) => { + if(!e) + this.resources[path.toString()] = new _FTPFileSystemResource(); + else if(e) + e = Errors.ResourceAlreadyExists; + + c.end(); + _callback(e); + } + + if(ctx.type.isDirectory) + c.mkdir(realPath, callback); + else + { + this._openWriteStream(path, { + context: ctx.context, + estimatedSize: 0, + mode: null, + targetSource: true + }, (e, wStream) => { + if(e) + return callback(e); + + wStream.end(new Buffer(0), callback) + }) + } + }) + } + + protected _delete(path : Path, ctx : DeleteInfo, _callback : SimpleCallback) : void + { + if(path.isRoot()) + return _callback(Errors.InvalidOperation); + + const { realPath } = this.getRealPath(path); + + this.connect((c) => { + const callback = (e) => { + if(!e) + delete this.resources[path.toString()]; + + c.end(); + _callback(e); + } + + this.type(ctx.context, path, (e, type) => { + if(e) + return callback(Errors.ResourceNotFound); + + if(type.isDirectory) + c.rmdir(realPath, callback); + else + c.delete(realPath, callback); + }) + }) + } + + protected _openWriteStream(path : Path, ctx : OpenWriteStreamInfo, callback : ReturnCallback) : void + { + if(path.isRoot()) + return callback(Errors.InvalidOperation); + + const { realPath, resource } = this.getRealPath(path); + + this.connect((c) => { + const wStream = new Transform({ + transform(chunk, encoding, cb) + { + cb(null, chunk); + } + }); + c.put(wStream, realPath, (e) => { + c.end(); + }); + callback(null, wStream); + }) + } + + protected _openReadStream(path : Path, ctx : OpenReadStreamInfo, callback : ReturnCallback) : void + { + if(path.isRoot()) + return callback(Errors.InvalidOperation); + + const { realPath } = this.getRealPath(path); + + this.connect((c) => { + c.get(realPath, (e, rStream) => { + if(e) + return callback(Errors.ResourceNotFound, null); + + const stream = new Transform({ + transform(chunk, encoding, cb) + { + cb(null, chunk); + } + }); + stream.on('error', () => { + c.end(); + }) + stream.on('finish', () => { + c.end(); + }) + rStream.pipe(stream); + callback(null, stream); + }); + }) + } + + protected _move(pathFrom : Path, pathTo : Path, ctx : MoveInfo, callback : ReturnCallback) : void + { + if(pathFrom.isRoot()) + return callback(Errors.InvalidOperation); + if(pathTo.isRoot()) + return callback(Errors.InvalidOperation); + + const { realPath: realPathFrom } = this.getRealPath(pathFrom); + const { realPath: realPathTo } = this.getRealPath(pathTo); + + this.connect((c) => { + c.rename(realPathFrom, realPathTo, (e) => { + if(!e) + { + this.resources[realPathTo] = this.resources[realPathFrom]; + delete this.resources[realPathFrom]; + c.end(); + callback(null, true); + } + else + { + c.lastMod(realPathTo, (er) => { + if(!er) + e = Errors.ResourceAlreadyExists; + else + e = Errors.ResourceNotFound; + c.end(); + callback(e, false); + }) + } + }) + }) + } + + protected _size(path : Path, ctx : SizeInfo, callback : ReturnCallback) : void + { + if(path.isRoot()) + return callback(Errors.InvalidOperation); + + const { realPath } = this.getRealPath(path); + + this.connect((c) => { + c.size(realPath, (e, size) => { + c.end(); + + callback(e ? Errors.ResourceNotFound : null, size); + }) + }) + } + + protected _lockManager(path : Path, ctx : LockManagerInfo, callback : ReturnCallback) : void + { + let resource = this.resources[path.toString()]; + if(!resource) + { + resource = new _FTPFileSystemResource(); + this.resources[path.toString()] = resource; + } + + callback(null, resource.locks); + } + + protected _propertyManager(path : Path, ctx : PropertyManagerInfo, callback : ReturnCallback) : void + { + let resource = this.resources[path.toString()]; + if(!resource) + { + resource = new _FTPFileSystemResource(); + this.resources[path.toString()] = resource; + } + + callback(null, resource.props); + } + + protected _readDir(path : Path, ctx : ReadDirInfo, callback : ReturnCallback) : void + { + const { realPath } = this.getRealPath(path); + + this.connect((c) => { + c.list(realPath, (e, list) => { + c.end(); + + if(e) + return callback(Errors.ResourceNotFound); + + callback(null, list.map((el) => el.name)); + }) + }); + } + + protected _creationDate(path : Path, ctx : CreationDateInfo, callback : ReturnCallback) : void + { + this._lastModifiedDate(path, { + context: ctx.context + }, callback); + } + + protected _lastModifiedDate(path : Path, ctx : LastModifiedDateInfo, callback : ReturnCallback) : void + { + if(path.isRoot()) + return callback(null, 0); + + const { realPath } = this.getRealPath(path); + + this.connect((c) => { + c.lastMod(realPath, (e, date) => { + c.end(); + callback(e ? Errors.ResourceNotFound : null, !date ? 0 : date.valueOf()); + }) + }) + } + + protected _type(path : Path, ctx : TypeInfo, callback : ReturnCallback) : void + { + if(path.isRoot()) + return callback(null, ResourceType.Directory); + + const { realPath } = this.getRealPath(path.getParent()); + + this.connect((c) => { + c.list(realPath, (e, list) => { + c.end(); + + if(e) + return callback(Errors.ResourceNotFound); + + for(const element of list) + if(element.name === path.fileName()) + return callback(null, element.type === '-' ? ResourceType.File : ResourceType.Directory); + + callback(Errors.ResourceNotFound); + }) + }) + } +} diff --git a/src/manager/v2/instances/PhysicalFileSystem.ts b/src/manager/v2/instances/PhysicalFileSystem.ts new file mode 100644 index 00000000..0a404741 --- /dev/null +++ b/src/manager/v2/instances/PhysicalFileSystem.ts @@ -0,0 +1,284 @@ +import { + LocalPropertyManager, + LastModifiedDateInfo, + FileSystemSerializer, + OpenWriteStreamInfo, + PropertyManagerInfo, + OpenReadStreamInfo, + IPropertyManager, + LocalLockManager, + CreationDateInfo, + LockManagerInfo, + SimpleCallback, + ReturnCallback, + ResourceType, + ILockManager, + ReadDirInfo, + CreateInfo, + DeleteInfo, + FileSystem, + SizeInfo, + MoveInfo, + TypeInfo, +} from '../fileSystem/export' +import { Readable, Writable } from 'stream' +import { join as pathJoin } from 'path' +import { Errors } from '../../../Errors' +import { Path } from '../Path' +import * as fs from 'fs' + +export class _PhysicalFileSystemResource +{ + props : LocalPropertyManager + locks : LocalLockManager + + constructor(data ?: _PhysicalFileSystemResource) + { + if(!data) + { + this.props = new LocalPropertyManager(); + this.locks = new LocalLockManager(); + } + else + { + const rs = data as _PhysicalFileSystemResource; + this.props = rs.props; + this.locks = rs.locks; + } + } +} + +export class PhysicalSerializer implements FileSystemSerializer +{ + uid() : string + { + return 'PhysicalFSSerializer_1.0.0'; + } + + serialize(fs : PhysicalFileSystem, callback : ReturnCallback) : void + { + callback(null, { + resources: fs.resources, + rootPath: fs.rootPath + }); + } + + unserialize(serializedData : any, callback : ReturnCallback) : void + { + const fs = new PhysicalFileSystem(serializedData.rootPath); + fs.resources = serializedData.resources; + callback(null, fs); + } +} + +export class PhysicalFileSystem extends FileSystem +{ + resources : { + [path : string] : _PhysicalFileSystemResource + } + + constructor(public rootPath : string) + { + super(new PhysicalSerializer()); + + this.resources = { + '/': new _PhysicalFileSystemResource() + }; + } + + protected getRealPath(path : Path) + { + const sPath = path.toString(); + + return { + realPath: pathJoin(this.rootPath, sPath.substr(1)), + resource: this.resources[sPath] + }; + } + + protected _create(path : Path, ctx : CreateInfo, _callback : SimpleCallback) : void + { + const { realPath } = this.getRealPath(path); + + const callback = (e) => { + if(!e) + this.resources[path.toString()] = new _PhysicalFileSystemResource(); + else if(e) + e = Errors.ResourceAlreadyExists; + + _callback(e); + } + + if(ctx.type.isDirectory) + fs.mkdir(realPath, callback); + else + { + if(!fs.constants || !fs.constants.O_CREAT) + { // node v5.* and lower + fs.writeFile(realPath, new Buffer(0), callback); + } + else + { // node v6.* and higher + fs.open(realPath, fs.constants.O_CREAT, (e, fd) => { + if(e) + return callback(e); + fs.close(fd, callback); + }) + } + } + } + + protected _delete(path : Path, ctx : DeleteInfo, _callback : SimpleCallback) : void + { + const { realPath } = this.getRealPath(path); + + const callback = (e) => { + if(!e) + delete this.resources[path.toString()]; + _callback(e); + } + + this.type(ctx.context, path, (e, type) => { + if(e) + return callback(Errors.ResourceNotFound); + + if(type.isDirectory) + fs.rmdir(realPath, callback); + else + fs.unlink(realPath, callback); + }) + } + + protected _openWriteStream(path : Path, ctx : OpenWriteStreamInfo, callback : ReturnCallback) : void + { + const { realPath, resource } = this.getRealPath(path); + + fs.open(realPath, 'w+', (e, fd) => { + if(e) + return callback(Errors.ResourceNotFound); + + if(!resource) + this.resources[path.toString()] = new _PhysicalFileSystemResource(); + + callback(null, fs.createWriteStream(null, { fd })); + }) + } + + protected _openReadStream(path : Path, ctx : OpenReadStreamInfo, callback : ReturnCallback) : void + { + const { realPath } = this.getRealPath(path); + + fs.open(realPath, 'r', (e, fd) => { + if(e) + return callback(Errors.ResourceNotFound); + + callback(null, fs.createReadStream(null, { fd })); + }) + } + + protected _move(pathFrom : Path, pathTo : Path, ctx : MoveInfo, callback : ReturnCallback) : void + { + const { realPath: realPathFrom } = this.getRealPath(pathFrom); + const { realPath: realPathTo } = this.getRealPath(pathTo); + + fs.rename(realPathFrom, realPathTo, (e) => { + if(!e) + { + this.resources[realPathTo] = this.resources[realPathFrom]; + delete this.resources[realPathFrom]; + callback(null, true); + } + else + { + fs.stat(realPathTo, (er) => { + if(!er) + e = Errors.ResourceAlreadyExists; + else + e = Errors.ResourceNotFound; + callback(e, false); + }) + } + }) + } + + protected _size(path : Path, ctx : SizeInfo, callback : ReturnCallback) : void + { + const { realPath } = this.getRealPath(path); + + fs.stat(realPath, (e, stat) => { + if(e) + return callback(Errors.ResourceNotFound); + + callback(null, stat.size); + }) + } + + protected _lockManager(path : Path, ctx : LockManagerInfo, callback : ReturnCallback) : void + { + let resource = this.resources[path.toString()]; + if(!resource) + { + resource = new _PhysicalFileSystemResource(); + this.resources[path.toString()] = resource; + } + + callback(null, resource.locks); + } + + protected _propertyManager(path : Path, ctx : PropertyManagerInfo, callback : ReturnCallback) : void + { + let resource = this.resources[path.toString()]; + if(!resource) + { + resource = new _PhysicalFileSystemResource(); + this.resources[path.toString()] = resource; + } + + callback(null, resource.props); + } + + protected _readDir(path : Path, ctx : ReadDirInfo, callback : ReturnCallback) : void + { + const { realPath } = this.getRealPath(path); + + fs.readdir(realPath, (e, files) => { + callback(e ? Errors.ResourceNotFound : null, files); + }); + } + + protected _creationDate(path : Path, ctx : CreationDateInfo, callback : ReturnCallback) : void + { + const { realPath } = this.getRealPath(path); + + fs.stat(realPath, (e, stat) => { + if(e) + return callback(Errors.ResourceNotFound); + + callback(null, stat.birthtime.valueOf()); + }) + } + + protected _lastModifiedDate(path : Path, ctx : LastModifiedDateInfo, callback : ReturnCallback) : void + { + const { realPath } = this.getRealPath(path); + + fs.stat(realPath, (e, stat) => { + if(e) + return callback(Errors.ResourceNotFound); + + callback(null, stat.mtime.valueOf()); + }) + } + + protected _type(path : Path, ctx : TypeInfo, callback : ReturnCallback) : void + { + const { realPath } = this.getRealPath(path); + + fs.stat(realPath, (e, stat) => { + if(e) + return callback(Errors.ResourceNotFound); + + callback(null, stat.isDirectory() ? ResourceType.Directory : ResourceType.File); + }) + } +} diff --git a/src/manager/v2/instances/VirtualFileSystem.ts b/src/manager/v2/instances/VirtualFileSystem.ts new file mode 100644 index 00000000..951c6c64 --- /dev/null +++ b/src/manager/v2/instances/VirtualFileSystem.ts @@ -0,0 +1,279 @@ +import { + LocalPropertyManager, + LastModifiedDateInfo, + FileSystemSerializer, + OpenWriteStreamInfo, + PropertyManagerInfo, + OpenReadStreamInfo, + IPropertyManager, + LocalLockManager, + CreationDateInfo, + LockManagerInfo, + SimpleCallback, + ReturnCallback, + ResourceType, + ILockManager, + ReadDirInfo, + CreateInfo, + DeleteInfo, + FileSystem, + SizeInfo, + MoveInfo, + TypeInfo, +} from '../fileSystem/export' +import { Readable, Writable } from 'stream' +import { RequestContext } from '../../../server/v2/RequestContext' +import { Errors } from '../../../Errors' +import { Path } from '../Path' + +export class _VirtualFileSystemResource +{ + props : LocalPropertyManager + locks : LocalLockManager + content : Buffer[] + size : number + lastModifiedDate : number + creationDate : number + type : ResourceType + + constructor(data : _VirtualFileSystemResource | ResourceType) + { + if(data.constructor === ResourceType) + { + this.lastModifiedDate = Date.now(); + this.creationDate = Date.now(); + this.content = []; + this.props = new LocalPropertyManager(); + this.locks = new LocalLockManager(); + this.type = data as ResourceType; + this.size = 0; + } + else + { + const rs = data as _VirtualFileSystemResource; + this.lastModifiedDate = rs.lastModifiedDate; + this.creationDate = rs.creationDate; + this.content = rs.content; + this.props = rs.props; + this.locks = rs.locks; + this.size = rs.size; + this.type = rs.type; + } + } + + static updateLastModified(r : _VirtualFileSystemResource) + { + r.lastModifiedDate = Date.now(); + } +} + +export class VirtualFileReadable extends Readable +{ + blockIndex : number + + constructor(public contents : Int8Array[]) + { + super(); + + this.blockIndex = -1; + } + + _read(size : number) + { + while(true) + { + ++this.blockIndex; + + if(this.blockIndex >= this.contents.length) + { + this.push(null); + break; + } + + if(!this.push(this.contents[this.blockIndex])) + break; + } + } +} + +export class VirtualFileWritable extends Writable +{ + constructor(public contents : Int8Array[]) + { + super(null); + } + + _write(chunk : Buffer | string | any, encoding : string, callback : (error : Error) => void) + { + this.contents.push(chunk); + callback(null); + } +} + +export class VirtualSerializer implements FileSystemSerializer +{ + uid() : string + { + return 'VirtualFSSerializer_1.0.0'; + } + + serialize(fs : VirtualFileSystem, callback : ReturnCallback) : void + { + callback(null, fs.resources); + } + + unserialize(serializedData : any, callback : ReturnCallback) : void + { + const fs = new VirtualFileSystem(); + fs.resources = serializedData; + callback(null, fs); + } +} + +export class VirtualFileSystem extends FileSystem +{ + resources : { + [path : string] : _VirtualFileSystemResource + } + + constructor(serializer ?: FileSystemSerializer) + { + super(serializer ? serializer : new VirtualSerializer()); + + this.resources = { + '/': new _VirtualFileSystemResource(ResourceType.Directory) + }; + } + + protected _fastExistCheck(ctx : RequestContext, path : Path, callback : (exists : boolean) => void) : void + { + callback(this.resources[path.toString()] !== undefined); + } + + protected _create(path : Path, ctx : CreateInfo, callback : SimpleCallback) : void + { + this.resources[path.toString()] = new _VirtualFileSystemResource(ctx.type); + callback(); + } + + protected _delete(path : Path, ctx : DeleteInfo, callback : SimpleCallback) : void + { + delete this.resources[path.toString()]; + callback(); + } + + protected _openWriteStream(path : Path, ctx : OpenWriteStreamInfo, callback : ReturnCallback) : void + { + const resource = this.resources[path.toString()]; + if(resource === undefined) + return callback(Errors.ResourceNotFound); + + const content = []; + const stream = new VirtualFileWritable(content); + stream.on('finish', () => { + resource.content = content; + resource.size = content.map((c) => c.length).reduce((s, n) => s + n, 0); + _VirtualFileSystemResource.updateLastModified(resource); + }) + callback(null, stream); + } + + protected _openReadStream(path : Path, ctx : OpenReadStreamInfo, callback : ReturnCallback) : void + { + const resource = this.resources[path.toString()]; + if(resource === undefined) + return callback(Errors.ResourceNotFound); + + callback(null, new VirtualFileReadable(resource.content)); + } + + protected _move(pathFrom : Path, pathTo : Path, ctx : MoveInfo, callback : ReturnCallback) : void + { + const from = pathFrom.toString(); + const to = pathTo.toString(); + const existed = !!this.resources[to]; + const fromExists = !!this.resources[from]; + + if(!fromExists) + return callback(Errors.ResourceNotFound); + if(existed && !ctx.overwrite) + return callback(Errors.ResourceAlreadyExists); + + this.resources[to] = this.resources[from]; + delete this.resources[from]; + + callback(null, existed); + } + + protected _size(path : Path, ctx : SizeInfo, callback : ReturnCallback) : void + { + const resource = this.resources[path.toString()]; + if(!resource) + return callback(Errors.ResourceNotFound); + + callback(null, resource.size); + } + + protected _lockManager(path : Path, ctx : LockManagerInfo, callback : ReturnCallback) : void + { + const resource = this.resources[path.toString()]; + if(!resource) + return callback(Errors.ResourceNotFound); + + callback(null, resource.locks); + } + + protected _propertyManager(path : Path, ctx : PropertyManagerInfo, callback : ReturnCallback) : void + { + const resource = this.resources[path.toString()]; + if(!resource) + return callback(Errors.ResourceNotFound); + + callback(null, resource.props); + } + + protected _readDir(path : Path, ctx : ReadDirInfo, callback : ReturnCallback) : void + { + const base = path.toString(true); + const children = []; + + for(const subPath in this.resources) + { + if(subPath.indexOf(base) === 0) + { + const pSubPath = new Path(subPath); + if(pSubPath.paths.length === path.paths.length + 1) + children.push(pSubPath); + } + } + + callback(null, children); + } + + protected _creationDate(path : Path, ctx : CreationDateInfo, callback : ReturnCallback) : void + { + const resource = this.resources[path.toString()]; + if(!resource) + return callback(Errors.ResourceNotFound); + + callback(null, resource.creationDate); + } + + protected _lastModifiedDate(path : Path, ctx : LastModifiedDateInfo, callback : ReturnCallback) : void + { + const resource = this.resources[path.toString()]; + if(!resource) + return callback(Errors.ResourceNotFound); + + callback(null, resource.lastModifiedDate); + } + + protected _type(path : Path, ctx : TypeInfo, callback : ReturnCallback) : void + { + const resource = this.resources[path.toString()]; + if(!resource) + return callback(Errors.ResourceNotFound); + + callback(null, resource.type); + } +} diff --git a/src/resource/export.v2.ts b/src/resource/export.v2.ts new file mode 100644 index 00000000..62c27f1d --- /dev/null +++ b/src/resource/export.v2.ts @@ -0,0 +1,7 @@ + +// Locks +export * from './lock/LockScope' +export * from './lock/LockKind' +export * from './lock/LockType' +export * from './lock/LockBag' +export * from './lock/Lock' diff --git a/src/server/v2/RequestContext.ts b/src/server/v2/RequestContext.ts new file mode 100644 index 00000000..b2cce728 --- /dev/null +++ b/src/server/v2/RequestContext.ts @@ -0,0 +1,410 @@ +//import { IResource, ReturnCallback, ResourceType } from '../../resource/IResource' +import { requirePrivilege, BasicPrivilege } from '../../user/v2/privilege/IPrivilegeManager' +import { EventsName, DetailsType } from './webDAVServer/Events' +import { XML, XMLElement } from '../../helper/XML' +import { parseIfHeader } from '../../helper/v2/IfParser' +import { WebDAVServer } from './webDAVServer/WebDAVServer' +import { HTTPCodes } from '../HTTPCodes' +import { FileSystem } from '../../manager/v2/fileSystem/FileSystem' +import { ResourceType, ReturnCallback } from '../../manager/v2/fileSystem/CommonTypes' +import { Resource } from '../../manager/v2/fileSystem/Resource' +import { Path } from '../../manager/v2/Path' +import { Errors } from '../../Errors' +import { IUser } from '../../user/v2/IUser' +import * as http from 'http' +import * as url from 'url' + +export class RequestContextHeaders +{ + contentLength : number + isSource : boolean + depth : number + host : string + + constructor(protected request : http.IncomingMessage) + { + this.isSource = this.find('source', 'F').toUpperCase() === 'T' || this.find('translate', 'T').toUpperCase() === 'F'; + this.host = this.find('Host', 'localhost'); + + const depth = this.find('Depth'); + try + { + if(depth.toLowerCase() === 'infinity') + this.depth = -1; + else + this.depth = Math.max(-1, parseInt(depth, 10)); + } + catch(_) + { + this.depth = undefined; + } + + try + { + this.contentLength = Math.max(0, parseInt(this.find('Content-length', '0'), 10)); + } + catch(_) + { + this.contentLength = 0; + } + } + + find(name : string, defaultValue : string = null) : string + { + name = name.replace(/(-| )/g, '').toLowerCase(); + + for(const k in this.request.headers) + if(k.replace(/(-| )/g, '').toLowerCase() === name) + { + const value = this.request.headers[k].trim(); + if(value.length !== 0) + return value; + } + + return defaultValue; + } + + findBestAccept(defaultType : string = 'xml') : string + { + const accepts = this.find('Accept', 'text/xml').split(','); + const regex = { + 'xml': /[^a-z0-9A-Z]xml$/, + 'json': /[^a-z0-9A-Z]json$/ + }; + + for(const value of accepts) + { + for(const name in regex) + if(regex[name].test(value)) + return name; + } + + return defaultType; + } +} + +export interface RequestedResource +{ + path : Path + uri : string +} + +export interface RequestContextExternalOptions +{ + headers ?: { [name : string] : string } + url ?: string + user ?: IUser +} +export class DefaultRequestContextExternalOptions implements RequestContextExternalOptions +{ + headers : { [name : string] : string } = { + host: 'localhost' + } + url : string = '/' +} + +export class RequestContext +{ + headers : RequestContextHeaders + requested : RequestedResource + user : IUser + + protected constructor( + public server : WebDAVServer, + public request : http.IncomingMessage, + public response : http.ServerResponse, + public exit : () => void + ) { + this.headers = new RequestContextHeaders(request); + + const uri = url.parse(request.url).pathname; + this.requested = { + uri, + path: new Path(uri) + }; + } + + static createExternal(server : WebDAVServer) : RequestContext + static createExternal(server : WebDAVServer, callback : (error : Error, ctx : RequestContext) => void) : RequestContext + static createExternal(server : WebDAVServer, options : RequestContextExternalOptions) : RequestContext + static createExternal(server : WebDAVServer, options : RequestContextExternalOptions, callback : (error : Error, ctx : RequestContext) => void) : RequestContext + static createExternal(server : WebDAVServer, _options ?: RequestContextExternalOptions | ((error : Error, ctx : RequestContext) => void), _callback ?: (error : Error, ctx : RequestContext) => void) : RequestContext + { + const defaultValues = new DefaultRequestContextExternalOptions(); + + const options = _options && _options.constructor !== Function ? _options as RequestContextExternalOptions : defaultValues; + const callback = _callback ? _callback : _options && _options.constructor === Function ? _options as ((error : Error, ctx : RequestContext) => void) : () => {}; + + if(defaultValues !== options) + { + for(const name in defaultValues) + if(options[name] === undefined) + options[name] = defaultValues[name]; + } + + const ctx = new RequestContext(server, { + headers: options.headers, + url: options.url + } as any, null, null); + + if(!options.user) + server.httpAuthentication.getUser(ctx, (e, user) => { + ctx.user = options.user; + callback(e, ctx); + }) + + return ctx; + } + + static create(server : WebDAVServer, request : http.IncomingMessage, response : http.ServerResponse, callback : (error : Error, ctx : RequestContext) => void) + { + const ctx = new RequestContext(server, request, response, null); + response.setHeader('DAV', '1,2'); + response.setHeader('Server', server.options.serverName + '/' + server.options.version); + + ctx.askForAuthentication(false, (e) => { + if(e) + { + callback(e, ctx); + return; + } + + server.httpAuthentication.getUser(ctx, (e, user) => { + ctx.user = user; + if(e && e !== Errors.UserNotFound) + { + if(server.options.requireAuthentification || e !== Errors.MissingAuthorisationHeader) + return callback(e, ctx); + } + + if(server.options.requireAuthentification && (!user || user.isDefaultUser || e === Errors.UserNotFound)) + return callback(Errors.MissingAuthorisationHeader, ctx); + + server.getFileSystem(ctx.requested.path, (fs, _, subPath) => { + fs.type(ctx, subPath, (e, type) => { + if(e) + type = undefined; + + setAllowHeader(type); + }) + }) + }) + }) + + function setAllowHeader(type ?: ResourceType) + { + const allowedMethods = []; + for(const name in server.methods) + { + const method = server.methods[name]; + if(!method.isValidFor || method.isValidFor(type)) + allowedMethods.push(name.toUpperCase()); + } + + response.setHeader('Allow', allowedMethods.join(',')); + callback(null, ctx); + } + } + + noBodyExpected(callback : () => void) + { + if(this.server.options.strictMode && this.headers.contentLength !== 0) + { + this.setCode(HTTPCodes.UnsupportedMediaType); + this.exit(); + } + else + callback(); + } + + checkIfHeader(resource : Resource, callback : () => void) + checkIfHeader(fs : FileSystem, path : Path, callback : () => void) + checkIfHeader(_fs : FileSystem | Resource, _path : Path | (() => void), _callback ?: () => void) + { + const fs = _callback ? _fs as FileSystem : null; + const path = _callback ? _path as Path : null; + let resource = _callback ? null : _fs as Resource; + const callback = _callback ? _callback : _path as () => void; + + const ifHeader = this.headers.find('If'); + + if(!ifHeader) + { + callback(); + return; + } + + if(!resource) + { + resource = fs.resource(this, path); + } + + parseIfHeader(ifHeader)(this, resource, (e, passed) => { + if(e) + { + this.setCode(HTTPCodes.InternalServerError); + this.exit(); + } + else if(!passed) + { + this.setCode(HTTPCodes.PreconditionFailed); + this.exit(); + } + else + callback(); + }); + } + + requirePrivilegeEx(privileges : BasicPrivilege | BasicPrivilege[], callback : () => void) + requirePrivilegeEx(privileges : string | string[], callback : () => void) + requirePrivilegeEx(privileges : BasicPrivilege | BasicPrivilege[] | BasicPrivilege | BasicPrivilege[], callback : () => void) + {/* + requirePrivilege(privileges, this, resource, (e, can) => { + if(e) + { + this.setCode(HTTPCodes.InternalServerError); + this.exit(); + return; + } + + if(!can) + { + this.setCode(HTTPCodes.Unauthorized); + this.exit(); + return; + } + + callback(); + });*/ + callback(); + } + + requirePrivilege(privileges : BasicPrivilege | BasicPrivilege[], callback : (error : Error, can : boolean) => void) + requirePrivilege(privileges : string | string[], callback : (error : Error, can : boolean) => void) + requirePrivilege(privileges : BasicPrivilege | BasicPrivilege[] | BasicPrivilege | BasicPrivilege[], callback : (error : Error, can : boolean) => void) + { + //requirePrivilege(privileges, this, resource, callback); + callback(null, true); + } + + askForAuthentication(checkForUser : boolean, callback : (error : Error) => void) + { + if(checkForUser && this.user !== null && !this.user.isDefaultUser) + { + callback(Errors.AlreadyAuthenticated); + return; + } + + const auth = this.server.httpAuthentication.askForAuthentication(); + for(const name in auth) + this.response.setHeader(name, auth[name]); + callback(null); + } + + getResource(callback : ReturnCallback) + getResource(path : Path | string, callback : ReturnCallback) + getResource(_path : Path | string | ReturnCallback, _callback ?: ReturnCallback) + { + const path = _callback ? new Path(_path as Path | string) : this.requested.path; + const callback = _callback ? _callback : _path as ReturnCallback; + + this.server.getResource(this, path, callback); + } + +/* + findHeader(name : string, defaultValue : string = null) : string + { + name = name.replace(/(-| )/g, '').toLowerCase(); + + for(const k in this.request.headers) + if(k.replace(/(-| )/g, '').toLowerCase() === name) + return this.request.headers[k]; + + return defaultValue; + } + + getResource(callback : ReturnCallback) + { + callback(!this.resource ? Errors.ResourceNotFound : null, this.resource); + }*/ + +/* + invokeEvent(event : EventsName, subjectResource ?: IResource, details ?: DetailsType) + { + this.server.invoke(event, this, subjectResource, details); + } + wrapEvent(event : EventsName, subjectResource ?: IResource, details ?: DetailsType) + { + const oldExit = this.exit; + this.exit = () => { + if(Math.floor(this.response.statusCode / 100) === 2) + this.invokeEvent(event, subjectResource, details); + + oldExit(); + } + return this.exit; + } +*/ + fullUri(uri : string = null) + { + if(!uri) + uri = this.requested.uri; + + return (this.prefixUri() + uri).replace(/([^:])\/\//g, '$1/'); + } + + prefixUri() + { + return 'http://' + this.headers.host.replace('/', ''); + } +/* + getResourcePath(resource : IResource, callback : ReturnCallback) + { + if(!resource.parent) + callback(null, '/'); + else + resource.webName((e, name) => process.nextTick(() => { + this.getResourcePath(resource.parent, (e, parentName) => { + callback(e, parentName.replace(/\/$/, '') + '/' + name); + }) + })) + }*/ + + writeBody(xmlObject : XMLElement | object) + { + let content = XML.toXML(xmlObject); + + switch(this.headers.findBestAccept()) + { + default: + case 'xml': + this.response.setHeader('Content-Type', 'application/xml; charset="utf-8"'); + this.response.setHeader('Content-Length', content.length.toString()); + this.response.write(content); + break; + + case 'json': + content = XML.toJSON(content); + this.response.setHeader('Content-Type', 'application/json; charset="utf-8"'); + this.response.setHeader('Content-Length', content.length.toString()); + this.response.write(content); + break; + } + } + + setCode(code : number, message ?: string) + { + if(!message) + message = http.STATUS_CODES[code]; + if(!message) + { + this.response.statusCode = code; + } + else + { + this.response.statusCode = code; + this.response.statusMessage = message; + } + } +} +export default RequestContext; diff --git a/src/server/v2/WebDAVRequest.ts b/src/server/v2/WebDAVRequest.ts new file mode 100644 index 00000000..ee5f3966 --- /dev/null +++ b/src/server/v2/WebDAVRequest.ts @@ -0,0 +1,13 @@ +import { ResourceType } from '../../manager/v2/fileSystem/CommonTypes' +import { RequestContext } from './RequestContext' +import { Readable } from 'stream' + +export { RequestContext } from './RequestContext' +export { HTTPCodes } from '../HTTPCodes' + +export interface HTTPMethod +{ + unchunked?(ctx : RequestContext, data : Buffer, callback : () => void) : void + chunked?(ctx : RequestContext, inputStream : Readable, callback : () => void) : void + isValidFor?(type ?: ResourceType) : boolean +} diff --git a/src/server/v2/WebDAVServerOptions.ts b/src/server/v2/WebDAVServerOptions.ts new file mode 100644 index 00000000..88590f7d --- /dev/null +++ b/src/server/v2/WebDAVServerOptions.ts @@ -0,0 +1,60 @@ +import { HTTPBasicAuthentication } from '../../user/v2/authentication/HTTPBasicAuthentication' +import { HTTPDigestAuthentication } from '../../user/v2/authentication/HTTPDigestAuthentication' +import { FakePrivilegeManager } from '../../user/v2/privilege/FakePrivilegeManager' +import { HTTPAuthentication } from '../../user/v2/authentication/HTTPAuthentication' +import { Writable, Readable } from 'stream' +import { IPrivilegeManager } from '../../user/v2/privilege/IPrivilegeManager' +import { SimpleUserManager } from '../../user/v2/simple/SimpleUserManager' +import { RootResource } from '../../resource/std/RootResource' +import { IUserManager } from '../../user/v2/IUserManager' +import { VirtualFileSystem } from '../../manager/v2/instances/VirtualFileSystem' +import { FileSystem } from '../../manager/v2/fileSystem/FileSystem' +import { FileSystemSerializer } from '../../manager/v2/fileSystem/Serialization' +import * as https from 'https' + +export interface IAutoSave +{ + treeFilePath : string, + tempTreeFilePath : string, + onSaveError ?: (error : Error) => void, + streamProvider ?: (inputStream : Writable, callback : (outputStream ?: Writable) => void) => void +} + +export interface IAutoLoad +{ + treeFilePath : string, + serializers ?: FileSystemSerializer[], + streamProvider ?: (inputStream : Readable, callback : (outputStream ?: Readable) => void) => void +} + +export class WebDAVServerOptions +{ + requireAuthentification ?: boolean = false + httpAuthentication ?: HTTPAuthentication = new HTTPDigestAuthentication(new SimpleUserManager(), 'default realm') + privilegeManager ?: IPrivilegeManager = new FakePrivilegeManager() + rootFileSystem ?: FileSystem = new VirtualFileSystem() + lockTimeout ?: number = 3600 + strictMode ?: boolean = false + hostname ?: string = '::' + https ?: https.ServerOptions = null + port ?: number = 1900 + serverName ?: string = 'webdav-server' + version ?: string = '1.8.0' + autoSave ?: IAutoSave = null + autoLoad ?: IAutoLoad = null +} +export default WebDAVServerOptions; + +export function setDefaultServerOptions(options : WebDAVServerOptions) : WebDAVServerOptions +{ + const def = new WebDAVServerOptions(); + + if(!options) + return def; + + for(const name in def) + if(options[name] === undefined) + options[name] = def[name]; + + return options; +} diff --git a/src/server/v2/commands/Commands.ts b/src/server/v2/commands/Commands.ts new file mode 100644 index 00000000..f00f3f90 --- /dev/null +++ b/src/server/v2/commands/Commands.ts @@ -0,0 +1,31 @@ +import NotImplemented from './NotImplemented' +import Proppatch from './Proppatch' +import Propfind from './Propfind' +import Options from './Options' +import Delete from './Delete' +import Unlock from './Unlock' +import Mkcol from './Mkcol' +import Copy from './Copy' +import Lock from './Lock' +import Move from './Move' +import Head from './Head' +import Post from './Post' +import Put from './Put' +import Get from './Get' + +export default { + NotImplemented, + Proppatch, + Propfind, + Options, + Delete, + Unlock, + Mkcol, + Copy, + Lock, + Move, + Head, + Post, + Put, + Get +} diff --git a/src/server/v2/commands/Copy.ts b/src/server/v2/commands/Copy.ts new file mode 100644 index 00000000..983817cc --- /dev/null +++ b/src/server/v2/commands/Copy.ts @@ -0,0 +1,20 @@ +import { HTTPCodes, HTTPMethod, RequestContext } from '../WebDAVRequest' +import { ResourceType, SimpleCallback } from '../../../manager/v2/fileSystem/CommonTypes' +import { Path } from '../../../manager/v2/Path' +import { Workflow } from '../../../helper/Workflow' +import { Readable } from 'stream' +import { Errors } from '../../../Errors' +import { execute } from './Move' + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + execute(ctx, 'copy', 'canCopy', callback); + } + + isValidFor(type : ResourceType) + { + return !!type; + } +} diff --git a/src/server/v2/commands/Delete.ts b/src/server/v2/commands/Delete.ts new file mode 100644 index 00000000..a8567bfe --- /dev/null +++ b/src/server/v2/commands/Delete.ts @@ -0,0 +1,39 @@ +import { HTTPCodes, RequestContext, HTTPMethod } from '../WebDAVRequest' +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes' + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + ctx.noBodyExpected(() => { + ctx.getResource((e, r) => { + if(e) + { + ctx.setCode(HTTPCodes.NotFound) + callback() + return; + } + + ctx.checkIfHeader(r, () => { + //ctx.requirePrivilege([ 'canDelete' ], r, () => { + r.delete((e) => process.nextTick(() => { + if(e) + ctx.setCode(HTTPCodes.InternalServerError); + else + { + ctx.setCode(HTTPCodes.OK); + //ctx.invokeEvent('delete', r); + } + callback(); + })) + //}) + }) + }) + }) + } + + isValidFor(type : ResourceType) + { + return !!type; + } +} diff --git a/src/server/v2/commands/Get.ts b/src/server/v2/commands/Get.ts new file mode 100644 index 00000000..25c5752c --- /dev/null +++ b/src/server/v2/commands/Get.ts @@ -0,0 +1,130 @@ +import { HTTPCodes, RequestContext, HTTPMethod } from '../WebDAVRequest' +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes' +import { Errors } from '../../../Errors' +import { Transform } from 'stream' + +class RangedStream extends Transform +{ + nb : number; + + constructor(public min : number, public max : number) + { + super(); + + this.nb = 0; + } + + _transform(chunk: any, encoding: string, callback: Function) + { + if(this.nb < this.min) + { + this.nb += chunk.length; + if(this.nb > this.min) + { + chunk = chunk.slice(this.nb - this.min); + callback(null, chunk); + } + else + callback(null, new Buffer(0)); + } + else if(this.nb > this.max) + { + this.nb += chunk.length; + callback(null, new Buffer(0)); + } + else + { + this.nb += chunk.length; + if(this.nb > this.max) + chunk = chunk.slice(0, this.max - (this.nb - chunk.length)); + callback(null, chunk); + } + } +} + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + ctx.noBodyExpected(() => { + ctx.getResource((e, r) => { + ctx.checkIfHeader(r, () => { + const targetSource = ctx.headers.isSource; + + //ctx.requirePrivilegeEx(targetSource ? [ 'canRead', 'canSource', 'canGetMimeType' ] : [ 'canRead', 'canGetMimeType' ], () => { + r.type((e, type) => { + if(e) + { + ctx.setCode(e === Errors.ResourceNotFound ? HTTPCodes.NotFound : HTTPCodes.InternalServerError) + return callback(); + } + if(!type.isFile) + { + ctx.setCode(HTTPCodes.MethodNotAllowed) + return callback(); + } + + r.mimeType(targetSource, (e, mimeType) => process.nextTick(() => { + if(e) + { + ctx.setCode(e === Errors.ResourceNotFound ? HTTPCodes.NotFound : HTTPCodes.InternalServerError) + return callback(); + } + + r.openReadStream(targetSource, (e, rstream) => process.nextTick(() => { + if(e) + { + ctx.setCode(HTTPCodes.MethodNotAllowed); + return callback(); + } + //ctx.invokeEvent('read', r); + + const range = ctx.headers.find('Range'); + if(range) + { + const rex = /([0-9]+)/g; + const min = parseInt(rex.exec(range)[1], 10); + const max = parseInt(rex.exec(range)[1], 10); + + ctx.setCode(HTTPCodes.PartialContent); + ctx.response.setHeader('Accept-Ranges', 'bytes') + ctx.response.setHeader('Content-Type', mimeType) + ctx.response.setHeader('Content-Length', (max - min).toString()) + ctx.response.setHeader('Content-Range', 'bytes ' + min + '-' + max + '/*') + + rstream.on('end', callback); + rstream.pipe(new RangedStream(min, max)).pipe(ctx.response); + } + else + { + r.size(targetSource, (e, size) => { + if(e) + { + ctx.setCode(e === Errors.ResourceNotFound ? HTTPCodes.NotFound : HTTPCodes.InternalServerError) + callback(); + } + else + { + ctx.setCode(HTTPCodes.OK); + ctx.response.setHeader('Accept-Ranges', 'bytes') + ctx.response.setHeader('Content-Type', mimeType); + ctx.response.setHeader('Content-Length', size.toString()); + rstream.on('end', callback); + rstream.pipe(ctx.response); + } + }) + } + })) + })) + }) + //}) + }) + }) + }) + } + + isValidFor(type : ResourceType) + { + return type && type.isFile; + } +} diff --git a/src/server/v2/commands/Head.ts b/src/server/v2/commands/Head.ts new file mode 100644 index 00000000..7824a580 --- /dev/null +++ b/src/server/v2/commands/Head.ts @@ -0,0 +1,58 @@ +import { HTTPCodes, HTTPMethod, RequestContext } from '../WebDAVRequest' +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes' +import { Errors } from '../../../Errors' + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + ctx.noBodyExpected(() => { + ctx.getResource((e, r) => { + const targetSource = ctx.headers.isSource; + + ctx.checkIfHeader(r, () => { + //ctx.requirePrivilege(targetSource ? [ 'canRead', 'canSource', 'canGetMimeType' ] : [ 'canRead', 'canGetMimeType' ], r, () => { + r.type((e, type) => { + if(e) + { + ctx.setCode(e === Errors.ResourceNotFound ? HTTPCodes.NotFound : HTTPCodes.InternalServerError); + return callback(); + } + if(!type.isFile) + { + ctx.setCode(HTTPCodes.MethodNotAllowed) + return callback(); + } + + r.mimeType(targetSource, (e, mimeType) => process.nextTick(() => { + if(e) + { + ctx.setCode(e === Errors.ResourceNotFound ? HTTPCodes.NotFound : HTTPCodes.InternalServerError); + return callback(); + } + + r.size(targetSource, (e, size) => { + if(e) + ctx.setCode(e === Errors.ResourceNotFound ? HTTPCodes.NotFound : HTTPCodes.InternalServerError); + else + { + ctx.setCode(HTTPCodes.OK); + ctx.response.setHeader('Accept-Ranges', 'bytes') + ctx.response.setHeader('Content-Type', mimeType); + ctx.response.setHeader('Content-Length', size.toString()); + } + callback(); + }) + })) + }) + //}) + }) + }) + }) + } + + isValidFor(type : ResourceType) + { + return type && type.isFile; + } +} diff --git a/src/server/v2/commands/Mkcol.ts b/src/server/v2/commands/Mkcol.ts new file mode 100644 index 00000000..c3b71ff9 --- /dev/null +++ b/src/server/v2/commands/Mkcol.ts @@ -0,0 +1,54 @@ +import { HTTPCodes, HTTPMethod, RequestContext } from '../WebDAVRequest' +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes' +import { Path } from '../../../manager/v2/Path' +import { Errors } from '../../../Errors' +import * as path from 'path' + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + ctx.noBodyExpected(() => { + ctx.checkIfHeader(undefined, () => { + ctx.getResource((e, r) => { + ctx.getResource(r.path.getParent(), (e, rParent) => { + rParent.type((e, parentType) => { + if(e) + { + ctx.setCode(e === Errors.ResourceNotFound ? HTTPCodes.Conflict : HTTPCodes.InternalServerError); + callback(); + return; + } + if(!parentType.isDirectory) + { + ctx.setCode(HTTPCodes.Forbidden); + callback(); + return; + } + + r.create(ResourceType.Directory, (e) => { + if(e) + { + if(e === Errors.WrongParentTypeForCreation) + ctx.setCode(HTTPCodes.Conflict); + else if(e === Errors.ResourceAlreadyExists) + ctx.setCode(HTTPCodes.MethodNotAllowed); + else + ctx.setCode(HTTPCodes.InternalServerError); + } + else + ctx.setCode(HTTPCodes.Created) + callback(); + }) + }) + }) + }) + }) + }) + } + + isValidFor(type : ResourceType) + { + return !type; + } +} diff --git a/src/server/v2/commands/Move.ts b/src/server/v2/commands/Move.ts new file mode 100644 index 00000000..2ccbd223 --- /dev/null +++ b/src/server/v2/commands/Move.ts @@ -0,0 +1,86 @@ +import { HTTPCodes, HTTPMethod, RequestContext } from '../WebDAVRequest' +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes' +import { StandardMethods } from '../../../manager/v2/fileSystem/StandardMethods' +import { Path } from '../../../manager/v2/Path' +import { Errors } from '../../../Errors' + +export function execute(ctx : RequestContext, methodName : string, privilegeName : string, callback : () => void) +{ + ctx.noBodyExpected(() => { + ctx.getResource((e, r) => { + ctx.checkIfHeader(r, () => { + //ctx.requirePrivilege([ privilegeName ], r, () => { + const overwrite = ctx.headers.find('overwrite') === 'T'; + + let destination : any = ctx.headers.find('destination'); + if(!destination) + { + ctx.setCode(HTTPCodes.BadRequest); + callback(); + return; + } + + const startIndex = destination.indexOf('://'); + if(startIndex !== -1) + { + destination = destination.substring(startIndex + '://'.length) + destination = destination.substring(destination.indexOf('/')) // Remove the hostname + port + } + destination = new Path(destination); + + if(destination.toString() === ctx.requested.path.toString()) + { + ctx.setCode(HTTPCodes.Forbidden); + return callback(); + } + + const cb = (e ?: Error, overwritten ?: boolean) => + { + if(e === Errors.ResourceNotFound) + ctx.setCode(HTTPCodes.NotFound); + else if(e === Errors.InsufficientStorage) + ctx.setCode(HTTPCodes.InsufficientStorage); + else if(e === Errors.Locked) + ctx.setCode(HTTPCodes.Locked); + else if(e === Errors.ResourceAlreadyExists) + ctx.setCode(HTTPCodes.Conflict); + else if(e) + ctx.setCode(HTTPCodes.InternalServerError); + else if(overwritten) + ctx.setCode(HTTPCodes.NoContent); + else + ctx.setCode(HTTPCodes.Created); + callback(); + }; + + ctx.server.getFileSystem(destination, (destFs, destRootPath, destSubPath) => { + if(destFs !== r.fs) + { // Copy + if(methodName === 'move') + StandardMethods.standardMove(ctx, r.path, r.fs, destSubPath, destFs, overwrite, cb); + else + StandardMethods.standardCopy(ctx, r.path, r.fs, destSubPath, destFs, overwrite, cb); + } + else + { // Delegate the operation to the file system + r[methodName](destination, overwrite, cb); + } + }) + //}) + }) + }) + }) +} + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + execute(ctx, 'move', 'canMove', callback); + } + + isValidFor(type : ResourceType) + { + return !!type; + } +} diff --git a/src/server/v2/commands/NotImplemented.ts b/src/server/v2/commands/NotImplemented.ts new file mode 100644 index 00000000..1bb2b923 --- /dev/null +++ b/src/server/v2/commands/NotImplemented.ts @@ -0,0 +1,10 @@ +import { HTTPCodes, RequestContext, HTTPMethod } from '../WebDAVRequest' + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + ctx.setCode(HTTPCodes.NotImplemented); + callback(); + } +} diff --git a/src/server/v2/commands/Options.ts b/src/server/v2/commands/Options.ts new file mode 100644 index 00000000..01fcae87 --- /dev/null +++ b/src/server/v2/commands/Options.ts @@ -0,0 +1,12 @@ +import { HTTPCodes, RequestContext, HTTPMethod } from '../WebDAVRequest' + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + ctx.noBodyExpected(() => { + ctx.setCode(HTTPCodes.OK); + callback(); + }) + } +} diff --git a/src/server/v2/commands/Post.ts b/src/server/v2/commands/Post.ts new file mode 100644 index 00000000..5b3ddd48 --- /dev/null +++ b/src/server/v2/commands/Post.ts @@ -0,0 +1,3 @@ + +import Put from './Put' +export default Put diff --git a/src/server/v2/commands/Propfind.ts b/src/server/v2/commands/Propfind.ts new file mode 100644 index 00000000..4e1ad41a --- /dev/null +++ b/src/server/v2/commands/Propfind.ts @@ -0,0 +1,534 @@ +import { HTTPCodes, RequestContext, HTTPMethod } from '../WebDAVRequest' +import { XML, XMLElement } from '../../../helper/XML' +import { BasicPrivilege } from '../../../user/v2/privilege/IPrivilegeManager' +import { Workflow } from '../../../helper/Workflow' +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes' +import { Resource } from '../../../manager/v2/fileSystem/Resource' +import { Path } from '../../../manager/v2/Path' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import * as http from 'http' + + +function dateISO8601(ticks : number) : string +{ + // Adding date + const date = new Date(ticks); + let result = date.toISOString().substring(0, '0000-00-00T00:00:00'.length); + + // Adding timezone offset + let offset = date.getTimezoneOffset(); + result += offset < 0 ? '-' : '+' + offset = Math.abs(offset) + + let h = Math.ceil(offset / 60).toString(10); + while(h.length < 2) + h = '0' + h; + + let m = (offset % 60).toString(10); + while(m.length < 2) + m = '0' + m; + + result += h + ':' + m; + + return result; +} + +/* +function lockDiscovery(lockDiscoveryCache : any, ctx : RequestContext, path : Path, resource : IResource, callback : ReturnCallback) +{ + const cached = lockDiscoveryCache[path.toString()]; + if(cached) + { + callback(null, cached); + return; + } + + const _Callback = callback; + callback = (e, l) => { + if(!e) + lockDiscoveryCache[path.toString()] = l; + _Callback(e, l); + } + + ctx.requireErPrivilege('canListLocks', resource, (e, can) => { + if(e || !can) + { + callback(e, {}); + return; + } + + resource.getLocks((e, locks) => { + if(e === Errors.MustIgnore) + { + locks = []; + } + else if(e) + { + callback(e, null); + return; + } + + if(resource.parent) + { + const parentPath = path.getParent(); + lockDiscovery(lockDiscoveryCache, ctx, parentPath, resource.parent, (e, l) => { + if(e) + callback(e, null); + else + { + l[path.toString()] = locks; + callback(null, l); + } + }); + } + else + callback(null, { + [path.toString()]: locks + }); + }); + }) +} +*/ + +interface PropertyRule +{ + leftElements : XMLElement[] + mustDisplay : (propertyName : string) => boolean + mustDisplayValue : (propertyName : string) => boolean +} + +function parseRequestBody(ctx : RequestContext, data : Buffer) : PropertyRule +{ + const allTrue : PropertyRule = { + leftElements: [], + mustDisplay: () => true, + mustDisplayValue: () => true + } + const onlyName : PropertyRule = { + leftElements: [], + mustDisplay: () => true, + mustDisplayValue: () => false + } + + if(ctx.headers.contentLength <= 0) + return allTrue; + + try + { + const xml = XML.parse(data); + + const propfind = xml.find('DAV:propfind'); + + if(propfind.findIndex('DAV:propname') !== -1) + return onlyName; + + if(propfind.findIndex('DAV:allprop') !== -1) + return allTrue; + + const prop = propfind.find('DAV:prop'); + const fn = (name : string) => { + const index = prop.findIndex(name); + if(index === -1) + return false; + prop.elements.splice(index, 1); + return true; + }; + + return { + leftElements: prop.elements, + mustDisplay: fn, + mustDisplayValue: () => true + } + } + catch(ex) + { + return allTrue; + } +} + +function propstatStatus(status : number) +{ + return 'HTTP/1.1 ' + status + ' ' + http.STATUS_CODES[status]; +} + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + ctx.getResource((e, resource) => { + const lockDiscoveryCache = {}; + + ctx.checkIfHeader(resource, () => { + const targetSource = ctx.headers.isSource; + + const multistatus = XML.createElement('D:multistatus', { + 'xmlns:D': 'DAV:' + }) + + resource.type((e, type) => process.nextTick(() => { + if(e) + { + ctx.setCode(e === Errors.ResourceNotFound ? HTTPCodes.NotFound : HTTPCodes.InternalServerError); + return callback(); + } + + if(!type.isDirectory || ctx.headers.depth === 0) + { + addXMLInfo(resource, multistatus, (e) => { + if(!e) + done(multistatus); + else + { + if(e === Errors.ResourceNotFound) + ctx.setCode(HTTPCodes.NotFound); + else if(e === Errors.BadAuthentication) + ctx.setCode(HTTPCodes.Unauthorized); + else + ctx.setCode(HTTPCodes.InternalServerError); + callback(); + } + }) + return; + } + + //ctx.requirePrivilege('canGetChildren', resource, () => { + resource.readDir(true, (e, children) => process.nextTick(() => { + function err(e) + { + if(e === Errors.ResourceNotFound) + ctx.setCode(HTTPCodes.NotFound); + else if(e === Errors.BadAuthentication) + ctx.setCode(HTTPCodes.Unauthorized); + else + ctx.setCode(HTTPCodes.InternalServerError); + callback(); + } + + addXMLInfo(resource, multistatus, (e) => { + if(e) + return err(e); + + new Workflow() + .each(children, (childName, cb) => { + ctx.server.getResource(ctx, ctx.requested.path.getChildPath(childName), (e, r) => { + if(e) + return cb(e); + addXMLInfo(r, multistatus, cb); + }); + }) + .error(err) + .done(() => { + done(multistatus) + }); + }); + })) + //}) + })) + + function addXMLInfo(resource : Resource, multistatus, _callback) + { + const reqBody = parseRequestBody(ctx, data); + + const response = XML.createElement('D:response'); + const callback = (e ?: Error) => { + if(e === Errors.MustIgnore) + e = null; + else if(!e) + multistatus.add(response); + + _callback(e); + } + + const propstat = response.ele('D:propstat') + + /*const privileges : BasicPrivilege[] = [ + 'canGetCreationDate', 'canGetAvailableLocks', 'canGetLastModifiedDate', 'canGetMimeType', 'canGetProperties', 'canGetSize', 'canGetType', 'canGetWebName' + ]; + if(targetSource) + privileges.push('canSource'); + ctx.requireErPrivilege(privileges, resource, (e, can) => { + if(e) + { + callback(e); + return; + } + + if(!can) + { + callback(Errors.BadAuthentication); + return; + }*/ + + propstat.ele('D:status').add('HTTP/1.1 200 OK') + + const prop = propstat.ele('D:prop') + + let nb = 1; + function nbOut(error?) + { + if(nb > 0 && error) + { + nb = -1000; + return callback(error); + } + + --nb; + if(nb === 0) + { + if(reqBody.leftElements.length > 0) + { + const propstatError = response.ele('D:propstat'); + const prop = propstatError.ele('D:prop'); + propstatError.ele('D:status').add(propstatStatus(HTTPCodes.NotFound)); + + for(let i = 0; i < reqBody.leftElements.length; ++i) + if(reqBody.leftElements[i]) + prop.ele(reqBody.leftElements[i].name); + } + callback(); + } + } + + const tags : any = {}; + + function mustDisplayTag(name : string) + { + if(reqBody.mustDisplay('DAV:' + name)) + tags[name] = { + el: prop.ele('D:' + name), + value: reqBody.mustDisplayValue('DAV:' + name) + }; + else + tags[name] = { + value: false + }; + } + + mustDisplayTag('getlastmodified') + mustDisplayTag('lockdiscovery') + mustDisplayTag('supportedlock') + mustDisplayTag('creationdate') + mustDisplayTag('resourcetype') + mustDisplayTag('displayname') + mustDisplayTag('getetag') + + function displayValue(values : string[] | string, fn : () => void) + { + if(values.constructor === String ? tags[values as string].value : (values as string[]).some((n) => tags[n].value)) + { + ++nb; + process.nextTick(fn); + } + } + + displayValue('creationdate', () => + { + resource.creationDate((e, ticks) => process.nextTick(() => { + if(!e) + tags.creationdate.el.add(dateISO8601(ticks)); + + nbOut(e); + })) + }) + + displayValue('lockdiscovery', () => + {/* + ++nb; + lockDiscovery(lockDiscoveryCache, ctx, resource.path, resource, (e, l) => { + if(e) + { + nbOut(e); + return; + } + + for(const path in l) + { + for(const _lock of l[path]) + { + const lock : Lock = _lock; + const activelock = tags.lockdiscovery.el.ele('D:activelock'); + + activelock.ele('D:lockscope').ele('D:' + lock.lockKind.scope.value.toLowerCase()) + activelock.ele('D:locktype').ele('D:' + lock.lockKind.type.value.toLowerCase()) + activelock.ele('D:depth').add('Infinity') + if(lock.owner) + activelock.ele('D:owner').add(lock.owner) + activelock.ele('D:timeout').add('Second-' + (lock.expirationDate - Date.now())) + activelock.ele('D:locktoken').ele('D:href', undefined, true).add(lock.uuid) + activelock.ele('D:lockroot').ele('D:href', undefined, true).add(ctx.fullUri(path).replace(' ', '%20')) + } + } + + nbOut(null); + })*/ + + resource.listDeepLocks((e, locks) => { + if(e) + return nbOut(e); + + for(const path in locks) + { + for(const _lock of locks[path]) + { + const lock : Lock = _lock; + const activelock = tags.lockdiscovery.el.ele('D:activelock'); + + activelock.ele('D:lockscope').ele('D:' + lock.lockKind.scope.value.toLowerCase()) + activelock.ele('D:locktype').ele('D:' + lock.lockKind.type.value.toLowerCase()) + activelock.ele('D:depth').add('Infinity') + if(lock.owner) + activelock.ele('D:owner').add(lock.owner) + activelock.ele('D:timeout').add('Second-' + (lock.expirationDate - Date.now())) + activelock.ele('D:locktoken').ele('D:href', undefined, true).add(lock.uuid) + activelock.ele('D:lockroot').ele('D:href', undefined, true).add(ctx.fullUri(path).replace(' ', '%20')) + } + } + + nbOut(null); + }) + }) + + ++nb; + resource.type((e, type) => process.nextTick(() => { + if(e) + return nbOut(e); + + resource.fs.getFullPath(ctx, resource.path, (e, path) => { + if(e) + return nbOut(e); + + const p = ctx.fullUri(path.toString()).replace(' ', '%20'); + const href = p.lastIndexOf('/') !== p.length - 1 && type.isDirectory ? p + '/' : p; + response.ele('D:href', undefined, true).add(href); + response.ele('D:location').ele('D:href', undefined, true).add(p); + + if(tags.resourcetype.value && type.isDirectory) + tags.resourcetype.el.ele('D:collection') + + if(type.isFile) + { + mustDisplayTag('getcontentlength') + mustDisplayTag('getcontenttype') + + if(tags.getcontenttype.value) + { + ++nb; + resource.mimeType(targetSource, (e, mimeType) => process.nextTick(() => { + if(!e) + tags.getcontenttype.el.add(mimeType) + nbOut(e); + })) + } + + if(tags.getcontentlength.value) + { + ++nb; + resource.size(targetSource, (e, size) => process.nextTick(() => { + if(!e) + tags.getcontentlength.el.add(size === undefined || size === null || size.constructor !== Number ? 0 : size) + nbOut(e); + })) + } + } + + nbOut(); + }) + })) + + displayValue('displayname', () => + { + let methodDisplayName = resource.webName; + if(resource.displayName) + methodDisplayName = resource.displayName; + + methodDisplayName.bind(resource)((e, name) => process.nextTick(() => { + if(!e) + tags.displayname.el.add(name ? name : ''); + nbOut(e); + })) + }) + + displayValue('supportedlock', () => + { + resource.availableLocks((e, lockKinds) => process.nextTick(() => { + if(e) + { + nbOut(e); + return; + } + + lockKinds.forEach((lockKind) => { + const lockentry = tags.supportedlock.el.ele('D:lockentry') + + const lockscope = lockentry.ele('D:lockscope') + lockscope.ele('D:' + lockKind.scope.value.toLowerCase()) + + const locktype = lockentry.ele('D:locktype') + locktype.ele('D:' + lockKind.type.value.toLowerCase()) + }) + nbOut(); + })) + }) + + displayValue('getlastmodified', () => + { + resource.lastModifiedDate((e, lastModifiedDate) => process.nextTick(() => { + if(!e && tags.getlastmodified.value) + tags.getlastmodified.el.add(new Date(lastModifiedDate).toUTCString()) + nbOut(e); + })) + }) + + displayValue('getetag', () => + { + resource.etag((e, etag) => process.nextTick(() => { + if(!e && tags.getetag.value) + tags.getetag.el.add(etag); + nbOut(e); + })) + }) + + ++nb; + process.nextTick(() => { + resource.propertyManager((e, pm) => { + if(e) + return nbOut(e); + + pm.getProperties((e, properties) => { + if(e) + return nbOut(e); + + for(const name in properties) + { + if(reqBody.mustDisplay(name)) + { + const tag = prop.ele(name); + if(reqBody.mustDisplayValue(name)) + tag.add(properties[name]); + } + } + nbOut(); + }) + }) + }) + + nbOut(); + //}) + } + + function done(multistatus) + { + ctx.setCode(HTTPCodes.MultiStatus); + ctx.writeBody(multistatus); + callback(); + } + }) + }) + } + + isValidFor(type : ResourceType) + { + return !!type; + } +} diff --git a/src/server/v2/commands/Proppatch.ts b/src/server/v2/commands/Proppatch.ts new file mode 100644 index 00000000..f4c6247a --- /dev/null +++ b/src/server/v2/commands/Proppatch.ts @@ -0,0 +1,96 @@ +import { HTTPCodes, HTTPMethod, RequestContext } from '../WebDAVRequest' +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes' +import { STATUS_CODES } from 'http' +import { Workflow } from '../../../helper/Workflow' +import { XML } from '../../../helper/XML' +import { Errors } from '../../../Errors' + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + ctx.getResource((e, r) => { + ctx.checkIfHeader(r, () => { + //ctx.requirePrivilege([ 'canSetProperty', 'canRemoveProperty' ], r, () => { + const multistatus = XML.createElement('D:multistatus', { + 'xmlns:D': 'DAV:' + }); + const response = multistatus.ele('D:response'); + response.ele('D:href', undefined, true).add(ctx.fullUri()); + + try + { + const xml = XML.parse(data); + const root = xml.find('DAV:propertyupdate'); + + let finalize = function() + { + finalize = function() + { + ctx.setCode(HTTPCodes.MultiStatus); + ctx.writeBody(multistatus); + callback(); + } + } + + const notify = function(el : any, error : any) + { + const code = error ? HTTPCodes.Conflict : HTTPCodes.OK; + const propstat = response.ele('D:propstat'); + propstat.ele('D:prop').ele(el.name); + propstat.ele('D:status').add('HTTP/1.1 ' + code + ' ' + STATUS_CODES[code]); + } + + const execute = function(name : string, eventName : /*EventsName*/string, fnProp) + { + const list = root.findMany(name); + if(list.length === 0) + { + finalize(); + return; + } + + list.forEach(function(el) { + const els = el.find('DAV:prop').elements; + + new Workflow(false) + .each(els, fnProp) + .intermediate((el, e) => { + /*if(!e) + ctx.invokeEvent(eventName, r, el)*/ + notify(el, e) + }) + .done(() => finalize()) + }) + } + + r.propertyManager((e, pm) => { + if(e) + { + ctx.setCode(e === Errors.ResourceNotFound ? HTTPCodes.NotFound : HTTPCodes.InternalServerError); + return callback(); + } + + execute('DAV:set', 'setProperty', (el, callback) => { + pm.setProperty(el.name, el.elements, callback) + }) + execute('DAV:remove', 'removeProperty', (el, callback) => { + pm.removeProperty(el.name, callback) + }) + }) + } + catch(ex) + { + ctx.setCode(HTTPCodes.BadRequest); + callback(); + } + //}) + }) + }) + } + + isValidFor(type : ResourceType) + { + return !!type; + } +} diff --git a/src/server/v2/commands/Put.ts b/src/server/v2/commands/Put.ts new file mode 100644 index 00000000..988cd0a1 --- /dev/null +++ b/src/server/v2/commands/Put.ts @@ -0,0 +1,67 @@ +import { HTTPCodes, HTTPMethod, RequestContext } from '../WebDAVRequest' +import { ResourceType, OpenWriteStreamMode } from '../../../manager/v2/fileSystem/CommonTypes' +import { Errors, HTTPError } from '../../../Errors' +import { Readable } from 'stream' +import * as path from 'path' + +export default class implements HTTPMethod +{ + isValidFor(type : ResourceType) + { + return !type || type.isFile; + } + + chunked(ctx : RequestContext, inputStream : Readable, callback : () => void) + { + const targetSource = ctx.headers.isSource; + + ctx.getResource((e, r) => { + ctx.checkIfHeader(r, () => { + //ctx.requirePrivilege(targetSource ? [ 'canSource', 'canWrite' ] : [ 'canWrite' ], r, () => { + let mode : OpenWriteStreamMode = 'canCreate'; + r.type((e, type) => process.nextTick(() => { + if(e === Errors.ResourceNotFound) + { + mode = 'mustCreate'; + } + else if(e) + { + ctx.setCode(HTTPCodes.InternalServerError); + callback(); + return; + } + else if(!type.isFile) + { + ctx.setCode(HTTPCodes.MethodNotAllowed); + callback(); + return; + } + + r.openWriteStream(mode, targetSource, ctx.headers.contentLength, (e, wStream, created) => { + if(e) + { + ctx.setCode(e === Errors.IntermediateResourceMissing || e === Errors.WrongParentTypeForCreation ? HTTPCodes.Conflict : HTTPCodes.InternalServerError); + callback(); + return; + } + + inputStream.pipe(wStream); + wStream.on('finish', (e) => { + if(created) + ctx.setCode(HTTPCodes.Created); + else + ctx.setCode(HTTPCodes.OK); + //ctx.invokeEvent('write', r); + callback(); + }); + wStream.on('error', (e) => { + ctx.setCode(HTTPCodes.InternalServerError) + callback(); + }); + }) + })) + //}) + }) + }) + } +} diff --git a/src/server/v2/commands/Unlock.ts b/src/server/v2/commands/Unlock.ts new file mode 100644 index 00000000..5ec36eaa --- /dev/null +++ b/src/server/v2/commands/Unlock.ts @@ -0,0 +1,92 @@ +import { HTTPCodes, HTTPMethod, RequestContext } from '../WebDAVRequest' +import { ResourceType } from '../../../manager/v2/fileSystem/CommonTypes' +import { STATUS_CODES } from 'http' +import { LockScope } from '../../../resource/lock/LockScope' +import { LockKind } from '../../../resource/lock/LockKind' +import { LockType } from '../../../resource/lock/LockType' +import { Errors } from '../../../Errors' +import { Lock } from '../../../resource/lock/Lock' +import { XML } from '../../../helper/XML' + +export default class implements HTTPMethod +{ + unchunked(ctx : RequestContext, data : Buffer, callback : () => void) : void + { + if(!ctx.user) + { + ctx.setCode(HTTPCodes.Forbidden); + callback(); + return; + } + + ctx.noBodyExpected(() => { + let token = ctx.headers.find('Lock-Token'); + if(!token) + { + ctx.setCode(HTTPCodes.BadRequest); + callback(); + return; + } + token = token.replace('<', '').replace('>', '').trim(); + ctx.response.setHeader('Lock-Token', '<' + token + '>'); + + ctx.getResource((e, r) => { + ctx.checkIfHeader(r, () => { + /*ctx.requireErPrivilege([ 'canGetLock', 'canRemoveLock' ], r, (e, can) => { + if(e) + { + ctx.setCode(HTTPCodes.InternalServerError); + callback(); + return; + } + + if(!can) + { + ctx.setCode(HTTPCodes.Forbidden); + callback(); + return; + }*/ + + r.lockManager((e, lm) => { + if(e) + { + ctx.setCode(e === Errors.ResourceNotFound ? HTTPCodes.NotFound : HTTPCodes.InternalServerError); + return callback(); + } + + lm.getLock(token, (e, lock) => { + if(e || !lock) + { + ctx.setCode(HTTPCodes.Conflict); + return callback(); + } + + if(!!lock.userUid && lock.userUid !== ctx.user.uid) + { + ctx.setCode(HTTPCodes.Forbidden); + return callback(); + } + + lm.removeLock(lock.uuid, (e, done) => { + if(e || !done) + ctx.setCode(HTTPCodes.Forbidden); + else + { + //ctx.invokeEvent('unlock', r, lock); + ctx.setCode(HTTPCodes.NoContent); + } + callback(); + }) + }) + }) + //}) + }) + }) + }) + } + + isValidFor(type : ResourceType) + { + return !!type; + } +} \ No newline at end of file diff --git a/src/server/v2/export.ts b/src/server/v2/export.ts new file mode 100644 index 00000000..bc5f759a --- /dev/null +++ b/src/server/v2/export.ts @@ -0,0 +1,6 @@ + +export * from './WebDAVServerOptions' +export * from '../HTTPCodes' +export * from './RequestContext' +export * from './WebDAVRequest' +export * from './webDAVServer/WebDAVServer' diff --git a/src/server/v2/webDAVServer/BeforeAfter.ts b/src/server/v2/webDAVServer/BeforeAfter.ts new file mode 100644 index 00000000..396c19cc --- /dev/null +++ b/src/server/v2/webDAVServer/BeforeAfter.ts @@ -0,0 +1,43 @@ +import { RequestContext, HTTPMethod } from '../WebDAVRequest' + +export interface RequestListener +{ + (ctx : RequestContext, next : () => void) : void +} + +function invokeBARequest(collection : RequestListener[], base : RequestContext, callback) +{ + function callCallback() + { + if(callback) + process.nextTick(callback); + } + + if(collection.length === 0) + { + callCallback(); + return; + } + + let nb = collection.length + 1; + function next() + { + --nb; + if(nb === 0) + { + callCallback(); + } + else + process.nextTick(() => collection[collection.length - nb](base, next)) + } + next(); +} + +export function invokeBeforeRequest(base : RequestContext, callback) +{ + invokeBARequest(this.beforeManagers, base, callback); +} +export function invokeAfterRequest(base : RequestContext, callback) +{ + invokeBARequest(this.afterManagers, base, callback); +} \ No newline at end of file diff --git a/src/server/v2/webDAVServer/Events.ts b/src/server/v2/webDAVServer/Events.ts new file mode 100644 index 00000000..eb4ac904 --- /dev/null +++ b/src/server/v2/webDAVServer/Events.ts @@ -0,0 +1,80 @@ +import { RequestContext } from '../WebDAVRequest' +import { XMLElement } from '../../../helper/XML' +import { Path } from '../../../manager/v2/Path' +import { Resource } from '../../../manager/v2/fileSystem/Resource' +import { Lock } from '../../../resource/lock/Lock' + +export type EventsName = 'create' | 'delete' | 'copy' | 'move' + | 'lock' | 'refreshLock' | 'unlock' + | 'setProperty' | 'removeProperty' + | 'write' | 'read' + | 'addChild'; + +export type DetailsType = Resource | Path | Lock | XMLElement; +export type Listener = (ctx : RequestContext, subjectResource : Resource, details ?: DetailsType) => void; + +function getEventBag(_this : any, event ?: string) +{ + if(!_this.__events) + _this.__events = { }; + + if(event && !_this.__events[event]) + { + _this.__events[event] = { + all: [], + named: {} + }; + return _this.__events[event]; + } + + return event ? _this.__events[event] : _this.__events; +} + +export function invoke(event : EventsName, ctx : RequestContext, subjectResource ?: Resource, details ?: DetailsType) +{ + const events = getEventBag(this, event); + events.all.forEach((e) => process.nextTick(() => e(ctx, subjectResource, details))); +} + +export function register(event : EventsName, listener : Listener) +{ + const events = getEventBag(this, event); + events.all.push(listener) +} + +export function registerWithName(event : EventsName, name : string, listener : Listener) +{ + const events = getEventBag(this, event); + events.all.push(listener); + events.named[name] = listener; +} + +export function clear(event : EventsName) +{ + const events = getEventBag(this, event); + events.all = []; + events.named = {}; +} + +export function clearAll(event : EventsName) +{ + this.__events = { }; +} + +export function remove(event : EventsName, listener : Listener) +{ + const events = getEventBag(this, event); + events.all.indexOf(listener); +} + +export function removeByName(event : EventsName, name : string) +{ + const events = getEventBag(this, event); + const listener = events.named[name]; + if(listener) + { + delete events.named[name]; + events.all.splice(events.all.indexOf(listener), 1); + } +} + diff --git a/src/server/v2/webDAVServer/Persistence.ts b/src/server/v2/webDAVServer/Persistence.ts new file mode 100644 index 00000000..bb341b70 --- /dev/null +++ b/src/server/v2/webDAVServer/Persistence.ts @@ -0,0 +1,154 @@ +//import { RootFSManager, VirtualFSManager, PhysicalFSManager } from '../../manager/export' +import { FileSystem } from '../../../manager/v2/fileSystem/FileSystem' +import { SimpleCallback } from '../../../manager/v2/fileSystem/CommonTypes' +import { FileSystemSerializer, serialize, unserialize, SerializedData } from '../../../manager/v2/fileSystem/Serialization' +import { VirtualSerializer } from '../../../manager/v2/instances/VirtualFileSystem' +import { RequestContext } from '../RequestContext' +import { IAutoSave, IAutoLoad } from '../WebDAVServerOptions' +import { Readable } from 'stream' +import * as zlib from 'zlib' +import * as fs from 'fs' + +function defaultSerializers() +{ + return [ + new VirtualSerializer() + ]; +} + +export function load(data : SerializedData, serializers : FileSystemSerializer[], callback: (error : Error) => void) +{ + serializers = serializers ? serializers : defaultSerializers(); + + unserialize(data, serializers, (e, udata) => { + this.fileSystems = udata; + }) +} + +export function autoLoad(callback : SimpleCallback) +{ + const options : IAutoLoad = this.options.autoLoad; + const oStream = fs.createReadStream(options.treeFilePath); + const stream = oStream.pipe(zlib.createGunzip()); + + oStream.on('error', callback) + stream.on('error', callback) + + let streamProvider = options.streamProvider; + + if(!streamProvider) + streamProvider = (s, cb) => cb(s); + + streamProvider(stream, (s : Readable) => { + if(!s) + s = stream; + + let data = ''; + s.on('data', (chunk) => { + data += chunk.toString(); + }) + s.on('error', callback) + s.on('end', () => { + const obj = JSON.parse(data.toString()); + this.load(obj, options.serializers, callback); + }) + }) +} + +export function save(callback : (error : Error, obj : SerializedData) => void) +{ + serialize(this.fileSystems, callback); +} + +export function autoSave(options : IAutoSave) +{ + if(!options.streamProvider) + options.streamProvider = (s, cb) => cb(s); + if(!options.onSaveError) + options.onSaveError = () => {}; + + let saving = false; + let saveRequested = false; + this.afterRequest((ctx : RequestContext, next) => { + switch(ctx.request.method.toUpperCase()) + { + case 'PROPPATCH': + case 'DELETE': + case 'MKCOL': + case 'MOVE': + case 'COPY': + case 'POST': + case 'PUT': + // Avoid concurrent saving + if(saving) + { + saveRequested = true; + next(); + return; + } + + const save = function() + { + this.save((e, data) => { + if(e) + { + options.onSaveError(e); + next(); + } + else + { + const stream = zlib.createGzip(); + options.streamProvider(stream, (outputStream) => { + if(!outputStream) + outputStream = stream; + outputStream.pipe(fs.createWriteStream(options.tempTreeFilePath)); + + stream.end(JSON.stringify(data), (e) => { + if(e) + { + options.onSaveError(e); + next(); + return; + } + }); + + stream.on('close', () => { + fs.unlink(options.treeFilePath, (e) => { + if(e && e.code !== 'ENOENT') // An error other than ENOENT (no file/folder found) + { + options.onSaveError(e); + next(); + return; + } + + fs.rename(options.tempTreeFilePath, options.treeFilePath, (e) => { + if(e) + options.onSaveError(e); + next(); + }) + }) + }) + }) + } + }) + } + + saving = true; + next = () => { + if(saveRequested) + { + saveRequested = false; + save.bind(this)(); + } + else + saving = false; + } + save.bind(this)(); + break; + + default: + next(); + break; + } + }) +} diff --git a/src/server/v2/webDAVServer/StartStop.ts b/src/server/v2/webDAVServer/StartStop.ts new file mode 100644 index 00000000..5badb076 --- /dev/null +++ b/src/server/v2/webDAVServer/StartStop.ts @@ -0,0 +1,122 @@ +import { HTTPCodes, RequestContext, HTTPMethod } from '../WebDAVRequest' +import { WebDAVServerStartCallback } from './WebDAVServer' +import { Writable, Readable } from 'stream' +import { Errors, HTTPError } from '../../../Errors' +import { WebDAVServer } from './WebDAVServer' +import { autoSave } from './Persistence' +import { IAutoSave } from '../WebDAVServerOptions' +import * as https from 'https' +import * as http from 'http' +import * as zlib from 'zlib' +import * as fs from 'fs' + +export function start(port ?: number | WebDAVServerStartCallback, callback ?: WebDAVServerStartCallback) +{ + let _port : number = this.options.port; + let _callback : WebDAVServerStartCallback; + + if(port && port.constructor === Number) + { + _port = port as number; + if(callback) + { + if(callback instanceof Function) + _callback = callback; + else + throw Errors.IllegalArguments; + } + } + else if(port && port.constructor === Function) + { + _port = this.options.port; + _callback = port as WebDAVServerStartCallback; + if(callback) + throw Errors.IllegalArguments; + } + + if(!this.server) + { + const serverCreator = this.options.https ? (c) => https.createServer(this.options.https, c) : (c) => http.createServer(c); + this.server = serverCreator((req : http.IncomingMessage, res : http.ServerResponse) => + { + let method : HTTPMethod = this.methods[this.normalizeMethodName(req.method)]; + if(!method) + method = this.unknownMethod; + + RequestContext.create(this, req, res, (e, base) => { + if(e) + { + if(e === Errors.AuenticationPropertyMissing || e === Errors.MissingAuthorisationHeader || e === Errors.BadAuthentication || e === Errors.WrongHeaderFormat) + base.setCode(HTTPCodes.Unauthorized); + else + base.setCode(HTTPCodes.InternalServerError); + res.end(); + return; + } + + base.exit = () => + { + base.response.end(); + this.invokeAfterRequest(base, null); + }; + + if(!method.chunked) + { + const go = (data : Buffer) => + { + this.invokeBeforeRequest(base, () => { + method.unchunked(base, data, base.exit); + }) + } + + if(base.headers.contentLength <= 0) + { + go(new Buffer(0)); + } + else + { + const data = new Buffer(base.headers.contentLength); + let index = 0; + req.on('data', (chunk) => { + if(chunk.constructor === String) + chunk = new Buffer(chunk as string); + + for(let i = 0; i < chunk.length && index < data.length; ++i, ++index) + data[index] = (chunk as Buffer)[i]; + + if(index >= base.headers.contentLength) + go(data); + }); + } + } + else + { + this.invokeBeforeRequest(base, () => { + method.chunked(base, req, base.exit); + }) + } + }) + }) + + if(this.options.autoSave) + autoSave.bind(this)(this.options.autoSave); + } + + this.server.listen(_port, this.options.hostname, () => { + if(_callback) + _callback(this.server); + }); +} + +export function stop(callback : () => void) +{ + callback = callback ? callback : () => { }; + + if(this.server) + { + this.server.close(callback); + this.server = null; + } + else + process.nextTick(callback); +} diff --git a/src/server/v2/webDAVServer/Types.ts b/src/server/v2/webDAVServer/Types.ts new file mode 100644 index 00000000..2688ea27 --- /dev/null +++ b/src/server/v2/webDAVServer/Types.ts @@ -0,0 +1,25 @@ +/*import { WebDAVServerOptions, setDefaultServerOptions } from '../WebDAVServerOptions' +import { SerializedObject, unserialize, serialize } from '../../manager/ISerializer' +import { HTTPCodes, MethodCallArgs, WebDAVRequest } from '../WebDAVRequest' +import { IResource, ReturnCallback } from '../../resource/IResource' +import { FakePrivilegeManager } from '../../user/privilege/FakePrivilegeManager' +import { HTTPAuthentication } from '../../user/authentication/HTTPAuthentication' +import { IPrivilegeManager } from '../../user/privilege/IPrivilegeManager' +import { SimpleUserManager } from '../../user/simple/SimpleUserManager' +import { FSManager, FSPath } from '../../manager/FSManager' +import { Errors, HTTPError } from '../../Errors' +import { RootResource } from '../../resource/std/RootResource' +import { IUserManager } from '../../user/IUserManager' +import Commands from '../commands/Commands'*/ +import * as http from 'http' + +/* +export interface IResourceTreeNode +{ + r ?: IResource + resource ?: IResource + c ?: ResourceTreeNode[] + children ?: ResourceTreeNode[] +} +export type ResourceTreeNode = IResourceTreeNode | IResource | IResourceTreeNode[] | IResource[]; +*/ \ No newline at end of file diff --git a/src/server/v2/webDAVServer/WebDAVServer.ts b/src/server/v2/webDAVServer/WebDAVServer.ts new file mode 100644 index 00000000..14b25035 --- /dev/null +++ b/src/server/v2/webDAVServer/WebDAVServer.ts @@ -0,0 +1,336 @@ +import { WebDAVServerOptions, setDefaultServerOptions } from '../WebDAVServerOptions' +import { HTTPCodes, RequestContext, HTTPMethod } from '../WebDAVRequest' +import { HTTPAuthentication } from '../../../user/v2/authentication/HTTPAuthentication' +import { IPrivilegeManager } from '../../../user/v2/privilege/IPrivilegeManager' +import { FileSystem } from '../../../manager/v2/fileSystem/FileSystem' +import { ReturnCallback } from '../../../manager/v2/fileSystem/CommonTypes' +import { Resource } from '../../../manager/v2/fileSystem/Resource' +import { Readable } from 'stream' +import Commands from '../commands/Commands' +import { Path } from '../../../manager/v2/Path' + +import * as persistence from './Persistence' +import * as beforeAfter from './BeforeAfter' +import * as startStop from './StartStop' +//import * as events from './Events' +import * as https from 'https' +import * as http from 'http' + +export type WebDAVServerStartCallback = (server ?: http.Server) => void; + +export class WebDAVServer +{ + public httpAuthentication : HTTPAuthentication + public privilegeManager : IPrivilegeManager + public options : WebDAVServerOptions + public methods : { [methodName : string]: HTTPMethod } + + protected beforeManagers : beforeAfter.RequestListener[] + protected afterManagers : beforeAfter.RequestListener[] + protected unknownMethod : HTTPMethod + protected server : http.Server | https.Server + + protected fileSystems : { + [path : string] : FileSystem + } + + constructor(options ?: WebDAVServerOptions) + { + this.beforeManagers = []; + this.afterManagers = []; + this.methods = {}; + this.options = setDefaultServerOptions(options); + + this.httpAuthentication = this.options.httpAuthentication; + this.privilegeManager = this.options.privilegeManager; + this.fileSystems = { + '/': this.options.rootFileSystem + }; + + // Implement all methods in commands/Commands.ts + const commands : { [name : string] : any } = Commands; + for(const k in commands) + if(k === 'NotImplemented') + this.onUnknownMethod(new commands[k]()); + else + this.method(k, new commands[k]()); + } + + + rootFileSystem() : FileSystem + { + return this.fileSystems['/']; + } + + getResource(ctx : RequestContext, path : Path | string, callback : ReturnCallback) : void + { + path = new Path(path); + + this.getFileSystem(path, (fs, _, subPath) => { + callback(null, fs.resource(ctx, subPath)); + }) + } + + setFileSystem(path : Path | string, fs : FileSystem, callback : (successed ?: boolean) => void) : void + setFileSystem(path : Path | string, fs : FileSystem, override : boolean, callback : (successed ?: boolean) => void) : void + setFileSystem(path : Path | string, fs : FileSystem, _override : boolean | ((successed ?: boolean) => void), _callback ?: (successed ?: boolean) => void) : void + { + const override = _callback ? _override as boolean : undefined; + const callback = _callback ? _callback : _override as ((successed ?: boolean) => void); + + const result = this.setFileSystemSync(path, fs, override); + if(callback) + callback(result); + } + setFileSystemSync(path : Path | string, fs : FileSystem, override : boolean = true) : boolean + { + const sPath = new Path(path).toString(); + + if(!override && this.fileSystems[sPath]) + return false; + + this.fileSystems[sPath] = fs; + return true; + } + + removeFileSystem(path : Path | string, callback : (nbRemoved ?: number) => void) : void + removeFileSystem(fs : FileSystem, callback : (nbRemoved ?: number) => void) : void + removeFileSystem(fs : FileSystem, checkByReference : boolean, callback : (nbRemoved ?: number) => void) : void + removeFileSystem(fs_path : Path | string | FileSystem, _checkByReference : boolean | ((nbRemoved ?: number) => void), _callback ?: (nbRemoved ?: number) => void) : void + { + const checkByReference = _callback ? _checkByReference as boolean : false; + const callback = _callback ? _callback : _checkByReference as ((nbRemoved ?: number) => void); + + const result = this.removeFileSystemSync(fs_path as any, checkByReference); + if(callback) + callback(result); + } + + removeFileSystemSync(path : Path | string) : number + removeFileSystemSync(fs : FileSystem, checkByReference ?: boolean) : number + removeFileSystemSync(fs_path : Path | string | FileSystem, checkByReference : boolean = false) : number + { + if(fs_path.constructor === Path || fs_path.constructor === String) + { + const path = new Path(fs_path as (Path | string)).toString(); + if(this.fileSystems[path] === undefined) + return 0; + else + { + delete this.fileSystems[path]; + return 1; + } + } + else + { + const fs = fs_path as FileSystem; + let nb = 0; + + for(const name in this.fileSystems) + if(checkByReference && this.fileSystems[name] === fs || !checkByReference && this.fileSystems[name].serializer().uid() === fs.serializer().uid()) + { + ++nb; + delete this.fileSystems[name]; + } + return nb; + } + } + + getFileSystemPath(fs : FileSystem, callback : (path : Path) => void) : void + getFileSystemPath(fs : FileSystem, checkByReference : boolean, callback : (path : Path) => void) : void + getFileSystemPath(fs : FileSystem, _checkByReference : boolean | ((path : Path) => void), _callback ?: (path : Path) => void) : void + { + const checkByReference = _callback ? _checkByReference as boolean : undefined; + const callback = _callback ? _callback : _checkByReference as ((path : Path) => void); + + callback(this.getFileSystemPathSync(fs, checkByReference)); + } + getFileSystemPathSync(fs : FileSystem, checkByReference ?: boolean) : Path + { + checkByReference = checkByReference === null || checkByReference === undefined ? true : checkByReference; + + for(const path in this.fileSystems) + if(checkByReference && this.fileSystems[path] === fs || !checkByReference && this.fileSystems[path].serializer().uid() === fs.serializer().uid()) + return new Path(path); + return null; + } + + getChildFileSystems(parentPath : Path, callback : (fss : { fs : FileSystem, path : Path }[]) => void) : void + { + const result = this.getChildFileSystemsSync(parentPath); + callback(result); + } + getChildFileSystemsSync(parentPath : Path) : { fs : FileSystem, path : Path }[] + { + const results : { fs : FileSystem, path : Path }[] = []; + const seekPath = parentPath.toString(true); + + for(const fsPath in this.fileSystems) + { + const pfsPath = new Path(fsPath); + if(pfsPath.paths.length === parentPath.paths.length + 1 && fsPath.indexOf(seekPath) === 0) + results.push({ + fs: this.fileSystems[fsPath], + path: pfsPath + }); + } + + return results; + } + + getFileSystem(path : Path, callback : (fs : FileSystem, rootPath : Path, subPath : Path) => void) : void + { + const result = this.getFileSystemSync(path); + callback(result.fs, result.rootPath, result.subPath); + } + getFileSystemSync(path : Path) : { fs : FileSystem, rootPath : Path, subPath : Path } + { + let best : any = { + index: 0, + rootPath : new Path('/') + }; + + for(const fsPath in this.fileSystems) + { + const pfsPath = new Path(fsPath); + + if(path.paths.length < pfsPath.paths.length) + continue; + + let value = 0; + for(; value < pfsPath.paths.length; ++value) + if(pfsPath.paths[value] !== path.paths[value]) + { + value = -1; + break; + } + + if(best.index < value) + best = { + index: value, + rootPath: pfsPath + } + + if(value === path.paths.length) + break; // Found the best value possible. + } + + const subPath = path.clone(); + for(const _ of best.rootPath.paths) + subPath.removeRoot(); + + return { + fs: this.fileSystems[best.rootPath.toString()], + rootPath: best.rootPath, + subPath + }; + } + + + + + + + + + + + + +/* + getResourceFromPath(arg : MethodCallArgs, path : FSPath | string[] | string, callback : ReturnCallback) + getResourceFromPath(arg : MethodCallArgs, path : FSPath | string[] | string, rootResource : IResource, callback : ReturnCallback) + getResourceFromPath(arg : MethodCallArgs, path : FSPath | string[] | string, callbackOrRootResource : ReturnCallback | IResource, callback ?: ReturnCallback) + { + resource.getResourceFromPath.bind(this)(arg, path, callbackOrRootResource, callback); + } + + addResourceTree(resoureceTree : ResourceTreeNode, callback : (e : Error) => void) + addResourceTree(rootResource : IResource, resoureceTree : ResourceTreeNode, callback : (e : Error) => void) + addResourceTree(_rootResource : IResource | ResourceTreeNode, _resoureceTree : ResourceTreeNode | (() => void), _callback ?: (e : Error) => void) + { + resource.addResourceTree.bind(this)(_rootResource, _resoureceTree, _callback); + } +*/ + onUnknownMethod(unknownMethod : HTTPMethod) + { + this.unknownMethod = unknownMethod; + } + + // Start / Stop + start(port : number) + start(callback : WebDAVServerStartCallback) + start(port : number, callback : WebDAVServerStartCallback) + start(port ?: number | WebDAVServerStartCallback, callback ?: WebDAVServerStartCallback) + { + startStop.start.bind(this)(port, callback); + } + stop = startStop.stop + + // Persistence + autoLoad = persistence.autoLoad + load = persistence.load + save = persistence.save + + method(name : string, manager : HTTPMethod) + { + this.methods[this.normalizeMethodName(name)] = manager; + } + + protected normalizeMethodName(method : string) : string + { + return method.toLowerCase(); + } + + // Before / After execution + invokeBeforeRequest(base : RequestContext, callback) + { + beforeAfter.invokeBeforeRequest.bind(this)(base, callback); + } + invokeAfterRequest(base : RequestContext, callback) + { + beforeAfter.invokeAfterRequest.bind(this)(base, callback); + } + beforeRequest(manager : beforeAfter.RequestListener) + { + this.beforeManagers.push(manager); + } + afterRequest(manager : beforeAfter.RequestListener) + { + this.afterManagers.push(manager); + } + + // Events + /* + emit(event : events.EventsName, arg : MethodCallArgs, subjectResource ?: IResource | FSPath, details ?: events.DetailsType) + { + events.invoke.bind(this)(event, subjectResource, details); + } + + on(event : events.EventsName, listener : events.Listener) + on(event : events.EventsName, eventName : string, listener : events.Listener) + on(event : events.EventsName, eName_listener : string | (events.Listener), listener ?: events.Listener) + { + if(eName_listener.constructor === Function) + events.register.bind(this)(event, eName_listener); + else + events.registerWithName.bind(this)(event, eName_listener, listener); + } + clearEvent(event : events.EventsName) + { + events.clear.bind(this)(event); + } + clearEvents(event : events.EventsName) + { + events.clearAll.bind(this)(); + } + removeEvent(event : events.EventsName, listener : events.Listener) + removeEvent(event : events.EventsName, eventName : string) + removeEvent(event : events.EventsName, eName_listener : string | (events.Listener)) + { + if(eName_listener.constructor === Function) + events.remove.bind(this)(event, eName_listener); + else + events.removeByName.bind(this)(event, eName_listener); + }*/ +} diff --git a/src/user/v2/IUser.ts b/src/user/v2/IUser.ts new file mode 100644 index 00000000..4a656cda --- /dev/null +++ b/src/user/v2/IUser.ts @@ -0,0 +1,10 @@ + +export interface IUser +{ + uid : string + + isAdministrator : boolean + isDefaultUser : boolean + password : string + username : string +} diff --git a/src/user/v2/IUserManager.ts b/src/user/v2/IUserManager.ts new file mode 100644 index 00000000..1994ca8a --- /dev/null +++ b/src/user/v2/IUserManager.ts @@ -0,0 +1,6 @@ +import { IUser } from './IUser' + +export interface IUserManager +{ + getDefaultUser(callback : (user : IUser) => void) +} diff --git a/src/user/v2/authentication/HTTPAuthentication.ts b/src/user/v2/authentication/HTTPAuthentication.ts new file mode 100644 index 00000000..e7d9e992 --- /dev/null +++ b/src/user/v2/authentication/HTTPAuthentication.ts @@ -0,0 +1,11 @@ +import { RequestContext } from '../../../server/v2/RequestContext' +import { IUserManager } from '../IUserManager' +import { IUser } from '../IUser' + +export interface HTTPAuthentication +{ + askForAuthentication() : { + [headeName : string] : string + } + getUser(arg : RequestContext, callback : (error : Error, user ?: IUser) => void) : void +} diff --git a/src/user/v2/authentication/HTTPBasicAuthentication.ts b/src/user/v2/authentication/HTTPBasicAuthentication.ts new file mode 100644 index 00000000..e1603fed --- /dev/null +++ b/src/user/v2/authentication/HTTPBasicAuthentication.ts @@ -0,0 +1,51 @@ +import { HTTPAuthentication } from './HTTPAuthentication' +import { RequestContext } from '../../../server/v2/RequestContext' +import { ITestableUserManager } from '../userManager/ITestableUserManager' +import { Errors } from '../../../Errors' +import { IUser } from '../IUser' + +export class HTTPBasicAuthentication implements HTTPAuthentication +{ + constructor(public userManager : ITestableUserManager, public realm : string = 'realm') + { } + + askForAuthentication() + { + return { + 'WWW-Authenticate': 'Basic realm="' + this.realm + '"' + } + } + + getUser(arg : RequestContext, callback : (error : Error, user : IUser) => void) + { + const onError = (error : Error) => + { + this.userManager.getDefaultUser((defaultUser) => { + callback(error, defaultUser) + }) + } + + const authHeader = arg.headers.find('Authorization') + if(!authHeader) + { + onError(Errors.MissingAuthorisationHeader) + return; + } + if(!/^Basic \s*[a-zA-Z0-9]+=*\s*$/.test(authHeader)) + { + onError(Errors.WrongHeaderFormat); + return; + } + + const value = Buffer.from(/^Basic \s*([a-zA-Z0-9]+=*)\s*$/.exec(authHeader)[1], 'base64').toString().split(':', 2); + const username = value[0]; + const password = value[1]; + + this.userManager.getUserByNamePassword(username, password, (e, user) => { + if(e) + onError(e); // Errors.BadAuthentication + else + callback(null, user); + }); + } +} diff --git a/src/user/v2/authentication/HTTPDigestAuthentication.ts b/src/user/v2/authentication/HTTPDigestAuthentication.ts new file mode 100644 index 00000000..74f98521 --- /dev/null +++ b/src/user/v2/authentication/HTTPDigestAuthentication.ts @@ -0,0 +1,90 @@ +import { HTTPAuthentication } from './HTTPAuthentication' +import { RequestContext } from '../../../server/v2/RequestContext' +import { IListUserManager } from '../userManager/IListUserManager' +import { Errors } from '../../../Errors' +import { IUser } from '../IUser' +import * as crypto from 'crypto' + +function md5(value : string | Buffer) : string +{ + return crypto.createHash('md5').update(value).digest('hex'); +} + +export class HTTPDigestAuthentication implements HTTPAuthentication +{ + constructor(public userManager : IListUserManager, public realm : string = 'realm', public nonceSize : number = 50) + { } + + generateNonce() : string + { + const buffer = new Buffer(this.nonceSize); + for(let i = 0; i < buffer.length; ++i) + buffer[i] = Math.ceil(Math.random() * 256); + + return md5(buffer); + } + + askForAuthentication() + { + return { + 'WWW-Authenticate': 'Digest realm="' + this.realm + '", qop="auth,auth-int", nonce="' + this.generateNonce() + '", opaque="' + this.generateNonce() + '"' + } + } + + getUser(arg : RequestContext, callback : (error : Error, user : IUser) => void) + { + const onError = (error : Error) => + { + this.userManager.getDefaultUser((defaultUser) => { + callback(error, defaultUser) + }) + } + + let authHeader = arg.headers.find('Authorization') + if(!authHeader) + { + onError(Errors.MissingAuthorisationHeader) + return; + } + if(!/^Digest (\s*[a-zA-Z]+\s*=\s*(("(\\"|[^"])+")|([^,\s]+))\s*(,|$))+$/.test(authHeader)) + { + onError(Errors.WrongHeaderFormat); + return; + } + + authHeader = authHeader.substring(authHeader.indexOf(' ') + 1); // remove the authentication type from the string + + const authProps : any = { }; + + const rex = /([a-zA-Z]+)\s*=\s*(?:(?:"((?:\\"|[^"])+)")|([^,\s]+))/g; + let match = rex.exec(authHeader); + while(match) + { + authProps[match[1]] = match[3] ? match[3] : match[2]; + match = rex.exec(authHeader); + } + + if(!(authProps.username && authProps.nonce && authProps.nc && authProps.cnonce && authProps.qop && authProps.response)) + { + onError(Errors.AuenticationPropertyMissing); + return; + } + + this.userManager.getUserByName(authProps.username, (e, user) => { + if(e) + { + onError(e); + return; + } + + const ha1 = md5(authProps.username + ':' + this.realm + ':' + (user.password ? user.password : '')); + const ha2 = md5(arg.request.method.toString().toUpperCase() + ':' + arg.requested.uri); + const result = md5(ha1 + ':' + authProps.nonce + ':' + authProps.nc + ':' + authProps.cnonce + ':' + authProps.qop + ':' + ha2); + + if(result.toLowerCase() === authProps.response.toLowerCase()) + callback(Errors.None, user); + else + onError(Errors.BadAuthentication); + }); + } +} diff --git a/src/user/v2/export.ts b/src/user/v2/export.ts new file mode 100644 index 00000000..69650a29 --- /dev/null +++ b/src/user/v2/export.ts @@ -0,0 +1,16 @@ + +export * from './authentication/HTTPDigestAuthentication' +export * from './authentication/HTTPBasicAuthentication' +export * from './authentication/HTTPAuthentication' +/* +export * from './privilege/SimplePathPrivilegeManager' +export * from './privilege/SimplePrivilegeManager' +export * from './privilege/FakePrivilegeManager' +export * from './privilege/IPrivilegeManager'*/ + +export * from './userManager/ITestableUserManager' +export * from './userManager/IListUserManager' +export * from './simple/SimpleUserManager' +export * from './simple/SimpleUser' +export * from './IUserManager' +export * from './IUser' diff --git a/src/user/v2/privilege/FakePrivilegeManager.ts b/src/user/v2/privilege/FakePrivilegeManager.ts new file mode 100644 index 00000000..bde72428 --- /dev/null +++ b/src/user/v2/privilege/FakePrivilegeManager.ts @@ -0,0 +1,24 @@ +import { SimplePrivilegeManager } from './SimplePrivilegeManager' +import { hasNoWriteLock } from './IPrivilegeManager' + +export class FakePrivilegeManager extends SimplePrivilegeManager +{ + constructor() + { + super(); + } + + canCreate = (arg, resource, callback) => callback(null, true) + canDelete = hasNoWriteLock + canWrite = hasNoWriteLock + canSource = (arg, resource, callback) => callback(null, true) + canRead = (arg, resource, callback) => callback(null, true) + canListLocks = (arg, resource, callback) => callback(null, true) + canSetLock = hasNoWriteLock + canGetAvailableLocks = (arg, resource, callback) => callback(null, true) + canAddChild = hasNoWriteLock + canRemoveChild = hasNoWriteLock + canGetChildren = (arg, resource, callback) => callback(null, true) + canSetProperty = hasNoWriteLock + canGetProperty = (arg, resource, callback) => callback(null, true) +} diff --git a/src/user/v2/privilege/IPrivilegeManager.ts b/src/user/v2/privilege/IPrivilegeManager.ts new file mode 100644 index 00000000..8dd3c7c7 --- /dev/null +++ b/src/user/v2/privilege/IPrivilegeManager.ts @@ -0,0 +1,104 @@ +import { RequestContext } from '../../../server/v2/RequestContext' +import { Resource } from '../../../manager/v2/export' +import { LockType } from '../../../resource/lock/LockType' + +export type PrivilegeManagerCallback = (error : Error, hasAccess : boolean) => void; +export type PrivilegeManagerMethod = (ctx : RequestContext, resource : Resource, callback : PrivilegeManagerCallback) => void + +export type BasicPrivilege = + 'all' + | 'canReadLocks' + | 'canWriteLocks' + | 'canWrite' + | 'canRead' + | 'canSee' + | 'canReadProperties' + | 'canWriteProperties' +/* +export type BasicPrivilege = + 'all' + | 'canCreate' + | 'canDelete' + | 'canMove' + | 'canRename' + | 'canAppend' + | 'canWrite' + | 'canRead' + | 'canSource' + | 'canGetMimeType' + | 'canGetSize' + | 'canListLocks' + | 'canSetLock' + | 'canRemoveLock' + | 'canGetAvailableLocks' + | 'canGetLock' + | 'canAddChild' + | 'canRemoveChild' + | 'canGetChildren' + | 'canSetProperty' + | 'canGetProperty' + | 'canGetProperties' + | 'canRemoveProperty' + | 'canGetCreationDate' + | 'canGetLastModifiedDate' + | 'canGetWebName' + | 'canGetType'; +*/ +export function requirePrivilege(privilege : string | BasicPrivilege | string[] | BasicPrivilege[], ctx : RequestContext, resource : Resource, callback : PrivilegeManagerCallback) +{/* + const privileges : string[] = privilege.constructor !== Array ? [ privilege as string ] : privilege as string[]; + const pm = ctx.server.privilegeManager; + + go(); + function go(error : Error = null, hasAccess : boolean = true) + { + if(privileges.length === 0 || error || !hasAccess) + { + process.nextTick(() => callback(error, hasAccess)); + return; + } + + process.nextTick(() => pm[privileges.shift()](ctx, resource, go)); + }*/ +} + +export interface IPrivilegeManager +{ + canCreate : PrivilegeManagerMethod + canDelete : PrivilegeManagerMethod + canMove : PrivilegeManagerMethod + canRename : PrivilegeManagerMethod + canAppend : PrivilegeManagerMethod + canWrite : PrivilegeManagerMethod + canRead : PrivilegeManagerMethod + canSource : PrivilegeManagerMethod + canGetMimeType : PrivilegeManagerMethod + canGetSize : PrivilegeManagerMethod + canListLocks : PrivilegeManagerMethod + canSetLock : PrivilegeManagerMethod + canRemoveLock : PrivilegeManagerMethod + canGetAvailableLocks : PrivilegeManagerMethod + canGetLock : PrivilegeManagerMethod + canAddChild : PrivilegeManagerMethod + canRemoveChild : PrivilegeManagerMethod + canGetChildren : PrivilegeManagerMethod + canSetProperty : PrivilegeManagerMethod + canGetProperty : PrivilegeManagerMethod + canGetProperties : PrivilegeManagerMethod + canRemoveProperty : PrivilegeManagerMethod + canGetCreationDate : PrivilegeManagerMethod + canGetLastModifiedDate : PrivilegeManagerMethod + canGetWebName : PrivilegeManagerMethod + canGetType : PrivilegeManagerMethod +} + +export function hasNoWriteLock(ctx : RequestContext, resource : Resource, callback : PrivilegeManagerCallback) +{/* + resource.getLocks((e, locks) => { + const hasNoLock = locks ? locks.filter((l) => (!l.userUid || l.userUid !== ctx.user.uid) && l.lockKind.type.isSame(LockType.Write)).length === 0 : false; + if(!hasNoLock || !resource.parent) + callback(e, hasNoLock); + else + hasNoWriteLock(ctx, resource.parent, callback); + });*/ +} diff --git a/src/user/v2/privilege/SimplePathPrivilegeManager.ts b/src/user/v2/privilege/SimplePathPrivilegeManager.ts new file mode 100644 index 00000000..156dd290 --- /dev/null +++ b/src/user/v2/privilege/SimplePathPrivilegeManager.ts @@ -0,0 +1,90 @@ +import { SimplePrivilegeManager, SimpleBasicPrivilege } from './SimplePrivilegeManager' +import { RequestContext } from '../../../server/v2/RequestContext' +import { hasNoWriteLock } from './IPrivilegeManager' +import { Resource } from '../../../manager/v2/export' +import { IUser } from '../IUser' + +function standarizePath(path : string) +{ + if(!path) + path = '/'; + + const startIndex = path.indexOf('://'); + if(startIndex !== -1) + { + path = path.substr(startIndex + 3); + path = path.substr(path.indexOf('/') + 1); + } + + path = path.replace(/\\/g, '/'); + const rex = /\/\//g; + while(rex.test(path)) + path = path.replace(rex, '/'); + path = path.replace(/\/$/g, ''); + path = path.replace(/^([^\/])/g, '/$1'); + if(path.length === 0) + path = '/'; + + return path; +} + +function checker(sppm : SimplePathPrivilegeManager, right : SimpleBasicPrivilege) +{ + return (ctx : RequestContext, resource : Resource, callback) => callback(null, sppm.can(ctx.user, ctx.requested.uri, right)); +} +function checkerNoLock(sppm : SimplePathPrivilegeManager, right : SimpleBasicPrivilege) +{ + return (ctx : RequestContext, resource : Resource, callback) => { + if(!sppm.can(ctx.user, ctx.requested.uri, right)) + callback(null, false); + else + hasNoWriteLock(ctx, resource, callback); + }; +} + +export class SimplePathPrivilegeManager extends SimplePrivilegeManager +{ + rights : any; + + constructor() + { + super(); + + this.rights = {}; + } + + setRights(user : IUser, path : string, rights : SimpleBasicPrivilege[]) + { + if(!this.rights[user.uid]) + this.rights[user.uid] = {}; + + this.rights[user.uid][standarizePath(path)] = rights; + } + getRights(user : IUser, path : string) : SimpleBasicPrivilege[] + { + if(!this.rights[user.uid]) + return []; + + return this.rights[user.uid][standarizePath(path)]; + } + can(user : IUser, path : string, right : SimpleBasicPrivilege) : boolean + { + const rights = this.getRights(user, path); + const r = rights && (rights.indexOf('all') !== -1 || rights.indexOf(right) !== -1); + return r; + } + + canCreate = checker(this, 'canCreate') + canDelete = checkerNoLock(this, 'canDelete') + canWrite = checkerNoLock(this, 'canWrite') + canSource = checker(this, 'canSource') + canRead = checker(this, 'canRead') + canListLocks = checker(this, 'canListLocks') + canSetLock = checkerNoLock(this, 'canSetLock') + canGetAvailableLocks = checker(this, 'canGetAvailableLocks') + canAddChild = checkerNoLock(this, 'canAddChild') + canRemoveChild = checkerNoLock(this, 'canRemoveChild') + canGetChildren = checker(this, 'canGetChildren') + canSetProperty = checkerNoLock(this, 'canSetProperty') + canGetProperty = checker(this, 'canGetProperty') +} diff --git a/src/user/v2/privilege/SimplePrivilegeManager.ts b/src/user/v2/privilege/SimplePrivilegeManager.ts new file mode 100644 index 00000000..a49a7bdf --- /dev/null +++ b/src/user/v2/privilege/SimplePrivilegeManager.ts @@ -0,0 +1,57 @@ +import { PrivilegeManagerMethod } from './IPrivilegeManager' +import { IPrivilegeManager } from './IPrivilegeManager' +import { RequestContext } from '../../../server/v2/RequestContext' +import { LockType } from '../../../resource/lock/LockType' + +export type SimpleBasicPrivilege = + 'all' + | 'canCreate' + | 'canDelete' + | 'canWrite' + | 'canSource' + | 'canRead' + | 'canListLocks' + | 'canSetLock' + | 'canGetAvailableLocks' + | 'canAddChild' + | 'canRemoveChild' + | 'canGetChildren' + | 'canSetProperty' + | 'canGetProperty'; + +export abstract class SimplePrivilegeManager implements IPrivilegeManager +{ + abstract canCreate : PrivilegeManagerMethod + abstract canDelete : PrivilegeManagerMethod + canMove = (ctx, resource, callback) => { + this.canDelete(ctx, resource, (e, v) => { + if(e || !v) + callback(e, v); + else + this.canRead(ctx, resource, callback); + }) + } + canRename = (ctx, resource, callback) => this.canWrite(ctx, resource, callback) + canAppend = (ctx, resource, callback) => this.canWrite(ctx, resource, callback) + abstract canWrite : PrivilegeManagerMethod + abstract canRead : PrivilegeManagerMethod + abstract canSource : PrivilegeManagerMethod + canGetMimeType = (ctx, resource, callback) => this.canRead(ctx, resource, callback) + canGetSize = (ctx, resource, callback) => this.canRead(ctx, resource, callback) + abstract canListLocks : PrivilegeManagerMethod + abstract canSetLock : PrivilegeManagerMethod + canRemoveLock = (ctx, resource, callback) => this.canSetLock(ctx, resource, callback) + abstract canGetAvailableLocks : PrivilegeManagerMethod + canGetLock = (ctx, resource, callback) => this.canListLocks(ctx, resource, callback) + abstract canAddChild : PrivilegeManagerMethod + abstract canRemoveChild : PrivilegeManagerMethod + abstract canGetChildren : PrivilegeManagerMethod + abstract canSetProperty : PrivilegeManagerMethod + abstract canGetProperty : PrivilegeManagerMethod + canGetProperties = (ctx, resource, callback) => this.canGetProperty(ctx, resource, callback) + canRemoveProperty = (ctx, resource, callback) => this.canSetProperty(ctx, resource, callback) + canGetCreationDate = (ctx, resource, callback) => this.canRead(ctx, resource, callback) + canGetLastModifiedDate = (ctx, resource, callback) => this.canRead(ctx, resource, callback) + canGetWebName = (ctx, resource, callback) => this.canRead(ctx, resource, callback) + canGetType = (ctx, resource, callback) => this.canRead(ctx, resource, callback) +} diff --git a/src/user/v2/simple/SimpleUser.ts b/src/user/v2/simple/SimpleUser.ts new file mode 100644 index 00000000..7e397ced --- /dev/null +++ b/src/user/v2/simple/SimpleUser.ts @@ -0,0 +1,16 @@ +import { IUser } from '../IUser' + +export class SimpleUser implements IUser +{ + uid : string + + constructor( + public username : string, + public password : string, + public isAdministrator : boolean, + public isDefaultUser : boolean + ) + { + this.uid = username; + } +} diff --git a/src/user/v2/simple/SimpleUserManager.ts b/src/user/v2/simple/SimpleUserManager.ts new file mode 100644 index 00000000..1c2f540f --- /dev/null +++ b/src/user/v2/simple/SimpleUserManager.ts @@ -0,0 +1,59 @@ +import { IListUserManager } from '../userManager/IListUserManager' +import { ITestableUserManager } from '../userManager/ITestableUserManager' +import { SimpleUser } from './SimpleUser' +import { Errors } from '../../../Errors' +import { IUser } from '../IUser' + +export class SimpleUserManager implements ITestableUserManager, IListUserManager +{ + protected users : any + + constructor() + { + this.users = { + __default: new SimpleUser('DefaultUser', null, false, true) + }; + } + + getUserByName(name : string, callback : (error : Error, user ?: IUser) => void) + { + if(!this.users[name]) + callback(Errors.UserNotFound); + else + callback(null, this.users[name]); + } + getDefaultUser(callback : (user : IUser) => void) + { + callback(this.users.__default); + } + + addUser(name : string, password : string, isAdmin : boolean = false) : IUser + { + const user = new SimpleUser(name, password, isAdmin, false); + this.users[name] = user; + return user; + } + + getUsers(callback : (error : Error, users : IUser[]) => void) + { + const users = []; + + for(const name in this.users) + users.push(this.users[name]); + + callback(null, users); + } + + getUserByNamePassword(name : string, password : string, callback : (error : Error, user ?: IUser) => void) : void + { + this.getUserByName(name, (e, user) => { + if(e) + return callback(e); + + if(user.password === password) + callback(null, user); + else + callback(Errors.UserNotFound); + }) + } +} diff --git a/src/user/v2/userManager/IListUserManager.ts b/src/user/v2/userManager/IListUserManager.ts new file mode 100644 index 00000000..34f33742 --- /dev/null +++ b/src/user/v2/userManager/IListUserManager.ts @@ -0,0 +1,8 @@ +import { IUser } from '../IUser' + +export interface IListUserManager +{ + getUserByName(name : string, callback : (error : Error, user ?: IUser) => void) + getDefaultUser(callback : (user : IUser) => void) + getUsers(callback : (error : Error, users ?: IUser[]) => void) +} diff --git a/src/user/v2/userManager/ITestableUserManager.ts b/src/user/v2/userManager/ITestableUserManager.ts new file mode 100644 index 00000000..7548dd7b --- /dev/null +++ b/src/user/v2/userManager/ITestableUserManager.ts @@ -0,0 +1,7 @@ +import { IUser } from '../IUser' + +export interface ITestableUserManager +{ + getDefaultUser(callback : (user : IUser) => void) + getUserByNamePassword(name : string, password : string, callback : (error : Error, user ?: IUser) => void) +}