UNPKG

tspace-nfs

Version:

tspace-nfs is a Network File System (NFS) and provides both server and client capabilities for accessing files over a network.

1,047 lines 67.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NfsServer = void 0; const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const fs_extra_1 = __importDefault(require("fs-extra")); const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); const xml_1 = __importDefault(require("xml")); const bcrypt_1 = __importDefault(require("bcrypt")); const node_cron_1 = __importDefault(require("node-cron")); const tspace_utils_1 = require("tspace-utils"); const html_minifier_terser_1 = require("html-minifier-terser"); const tspace_spear_1 = require("tspace-spear"); const queue_1 = __importDefault(require("./queue")); const default_html_1 = __importDefault(require("./default-html")); /** * The 'NfsServer' class is a created the server for nfs * * @example * import { NfsServer } from "tspace-nfs"; * * new NfsServer() * .listen(8000 , ({ port }) => console.log(`Server is running on port http://localhost:${port}`)) */ class NfsServer { constructor() { this._queue = new queue_1.default(3); this._fileExpired = 60 * 60; this._rootFolder = 'nfs'; this._cluster = false; this._jwtExipred = 60 * 60; this._jwtSecret = `<secret@${+new Date()}:${Math.floor(Math.random() * 9999)}>`; this._progress = false; this._debug = false; this._trash = '@trash'; this._backup = 30; this._default = ({ res }) => __awaiter(this, void 0, void 0, function* () { return res.html(this._html == null ? default_html_1.default : String(this._html)); }); this._benchmark = () => { return 'benchmark in nfs server'; }; this._media = ({ req, res, query, params }) => __awaiter(this, void 0, void 0, function* () { var _a; try { const { AccessKey, Expires, Signature, Download } = query; const bucket = params.bucket; if ([ AccessKey, Expires, Signature, Download, bucket ].some(v => v === '' || v == null)) { res.writeHead(400, { 'Content-Type': 'text/xml' }); const error = { Error: [ { Code: 'Bad request' }, { Message: 'The request was invalid' }, { Resource: req.url }, { RequestKey: query === null || query === void 0 ? void 0 : query.key } ] }; return res.end((0, xml_1.default)([error], { declaration: true })); } const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, ""); const combined = `@{${path}-${bucket}-${AccessKey}-${Expires}-${Download}}`; const compare = bcrypt_1.default.compareSync(combined, Buffer.from(Signature, 'base64').toString('utf-8')); const expired = Number.isNaN(+Expires) ? true : new Date(+Expires) < new Date(); if (!compare || expired) { res.writeHead(400, { 'Content-Type': 'text/xml' }); const error = { Error: [ { Code: expired ? 'Expired' : 'AccessDenied' }, { Message: expired ? 'Request has expired' : 'The signature is not correct' }, { Resource: req.url }, { RequestKey: query.key } ] }; return res.end((0, xml_1.default)([error], { declaration: true })); } const { stream, header, set } = yield this._makeStream({ bucket: bucket, filePath: String(path), range: (_a = req.headers) === null || _a === void 0 ? void 0 : _a.range, download: Download === Buffer.from(`${Expires}@true`).toString('base64').replace(/[=|?|&]+$/g, '') }); if (stream == null || header == null) { res.writeHead(404, { 'Content-Type': 'text/xml' }); const error = { Error: [ { Code: 'Not found' }, { Message: 'The file does not exist in our records' }, { Resource: req.url }, { RequestKey: query.key } ] }; return res.end((0, xml_1.default)([error], { declaration: true })); } set(res); return stream.pipe(res); } catch (err) { res.writeHead(400, { 'Content-Type': 'text/xml' }); const error = { Error: [ { Code: 'AccessDenied' }, { Message: err.message }, { Resource: req.url }, { RequestKey: query.key } ] }; return res.end((0, xml_1.default)([error], { declaration: true })); } }); this._apiFile = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () { try { const { bucket, token } = req; let { path, download, expired } = body; const fileName = `${path}`.replace(/^\/+/, ''); const directory = this._normalizeDirectory({ bucket, folder: null }); const fullPath = this._normalizePath({ directory, path: String(path), full: true }); if (!(yield this._fileExists(fullPath))) { if (this._debug) { console.log({ fullPath, path, download, expired }); } return res.status(404).json({ message: `No such directory or file, '${fileName}'` }); } const key = String(token); const expires = new tspace_utils_1.Time().addSeconds(expired == null || Number.isNaN(Number(expired)) ? this._fileExpired : Number(expired)).toTimeStamp(); const downloaded = `${Buffer.from(`${expires}@${download}`).toString('base64').replace(/[=|?|&]+$/g, '')}`; const combined = `@{${path}-${bucket}-${key}-${expires}-${downloaded}}`; const signature = Buffer.from(bcrypt_1.default.hashSync(combined, 1)).toString('base64'); return res.ok({ endpoint: [ `${bucket}/${fileName}?AccessKey=${key}`, `Expires=${expires}`, `Download=${downloaded}`, `Signature=${signature}` ].join('&') }); } catch (err) { if (this._debug) { console.log(err); } return res.status(500) .json({ message: err.message }); } }); this._apiBase64 = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () { try { const { bucket } = req; const { path: filename } = body; const directory = this._normalizeDirectory({ bucket, folder: null }); const path = this._normalizePath({ directory, path: String(filename), full: true }); if (!(yield this._fileExists(path))) { return res.status(404).json({ message: `no such file or directory, '${filename}'` }); } return res.json({ base64: fs_1.default.readFileSync(path, 'base64') }); } catch (err) { if (this._debug) { console.log(err); } return res.status(500).json({ message: err.message }); } }); this._apiStream = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () { try { const { bucket } = req; const { path: filename, range } = body; const directory = this._normalizeDirectory({ bucket, folder: null }); const fullPath = this._normalizePath({ directory, path: String(filename), full: true }); if (!(yield this._fileExists(fullPath))) { return res.status(404).json({ message: `no such file or directory, '${filename}'` }); } const stat = fs_1.default.statSync(fullPath); const fileSize = stat.size; if (range) { const parts = String(range).replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1; const chunksize = (end - start) + 1; const file = fs_1.default.createReadStream(fullPath, { start, end }); const head = { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4', }; res.writeHead(206, head); return file.pipe(res); } const head = { 'Content-Length': fileSize, 'Content-Type': 'video/mp4', }; res.writeHead(200, head); return fs_1.default.createReadStream(fullPath).pipe(res); } catch (err) { if (this._debug) { console.log(err); } throw err; } }); this._apiStorage = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () { try { const { bucket } = req; let { folder } = body; if (folder != null) { folder = this._normalizeFolder(String(folder)); } const directory = this._normalizeDirectory({ bucket, folder }); if (!(yield this._fileExists(directory))) { return res.status(404).json({ message: `No such directory or folder, '${folder}'` }); } const fileDirectories = yield this._files(directory, { ignore: this._trash }); const storage = fileDirectories.map((name) => { const stat = fs_1.default.statSync(name); return { name: path_1.default.relative(directory, name).replace(/\\/g, '/'), size: Number((stat.size / (1024 * 1024))) }; }); return res.ok({ storage }); } catch (err) { if (this._debug) { console.log(err); } return res.status(500).json({ message: err.message }); } }); this._apiFolders = ({ req, res }) => __awaiter(this, void 0, void 0, function* () { try { const { bucket } = req; const directory = this._normalizeDirectory({ bucket, folder: null }); const folders = fs_1.default.readdirSync(directory); return res.ok({ folders }); } catch (err) { if (this._debug) { console.log(err); } return res.status(500).json({ message: err.message }); } }); this._apiUpload = ({ req, res, files, body }) => __awaiter(this, void 0, void 0, function* () { try { const { bucket } = req; if (!Array.isArray(files === null || files === void 0 ? void 0 : files.file)) { return res.status(400).json({ message: 'The file is required.' }); } const file = files === null || files === void 0 ? void 0 : files.file[0]; if (file == null) { return res.status(400).json({ message: 'The file is required.' }); } let { folder } = body; if (folder != null) { folder = this._normalizeFolder(String(folder)); } const directory = this._normalizeDirectory({ bucket, folder }); if (!(yield this._fileExists(directory))) { if (this._debug) { console.log({ directory, bucket, folder }); } fs_1.default.mkdirSync(directory, { recursive: true }); } const writeFile = (file, to) => { return new Promise((resolve, reject) => { fs_1.default.createReadStream(file) .pipe(fs_1.default.createWriteStream(to)) .on('finish', () => { // remove temporary from chunked by nfs-client this._remove(to); // remove temporary from server this._remove(file, { delayMs: 0 }); return resolve(null); }) .on('error', (err) => reject(err)); return; }); }; yield writeFile(file.tempFilePath, this._normalizePath({ directory, path: file.name, full: true })); return res.ok({ path: this._normalizePath({ directory: folder, path: file.name }), name: file.name, size: file.size }); } catch (err) { if (this._debug) { console.log(err); } throw err; } }); this._apiMerge = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () { try { const { bucket } = req; let { folder, name, paths, totalSize } = body; if (folder != null) { folder = this._normalizeFolder(String(folder)); } const directory = this._normalizeDirectory({ bucket, folder }); if (!(yield this._fileExists(directory))) { fs_1.default.mkdirSync(directory, { recursive: true }); } const writeFile = (to) => __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { const writeStream = fs_1.default.createWriteStream(to, { flags: 'a' }); writeStream.on('error', (err) => { return reject(err); }); let processedSize = 0; const next = (index = 0) => { if (index >= paths.length) { writeStream.end(); writeStream.close(); return resolve(null); } const partPath = this._normalizePath({ directory, path: paths[index], full: true }); const readStream = fs_1.default.createReadStream(partPath, { highWaterMark: 1024 * 1024 * 100 }); if (this._progress) { readStream.on('data', (chunk) => { processedSize += chunk.length; const progress = ((processedSize / totalSize) * 100).toFixed(2); console.log(`The file '${path_1.default.basename(to)}' in progress: ${progress}%`); }); } readStream.on('error', (err) => { return reject(err); }); readStream.on('end', () => { this._remove(partPath, { delayMs: 0 }); next(index + 1); }); readStream.pipe(writeStream, { end: false }); }; next(); }); }); const to = this._normalizePath({ directory, path: name, full: true }); yield writeFile(to); return res.ok({ path: this._normalizePath({ directory: folder, path: name }), name: name, size: fs_1.default.statSync(to).size }); } catch (err) { if (this._debug) { console.log(err); } throw err; } }); this._apiUploadBase64 = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () { try { const { bucket } = req; let { folder, base64, name } = body; if (folder != null) { folder = this._normalizeFolder(String(folder)); } if (base64 === '' || base64 == null) { return res.status(400).json({ message: 'The base64 is required.' }); } if (name === '' || name == null) { return res.status(400).json({ message: 'The name is required.' }); } const directory = this._normalizeDirectory({ bucket, folder }); if (!(yield this._fileExists(directory))) { fs_1.default.mkdirSync(directory, { recursive: true }); } const writeFile = (base64, to) => { return fs_1.default.writeFileSync(to, String(base64), 'base64'); }; const to = path_1.default.join(path_1.default.resolve(), `${directory}/${name}`); writeFile(String(base64), to); return res.ok({ path: folder ? `${folder}/${name}` : name, name: name, size: fs_1.default.statSync(to).size }); } catch (err) { if (this._debug) { console.log(err); } throw err; } }); this._apiRemove = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () { try { const { bucket } = req; const { path: p } = body; const path = `${p}`.replace(/^\/+/, ''); const directory = this._normalizeDirectory({ bucket, folder: null }); const fullPath = this._normalizePath({ directory, path: path, full: true }); if (!(yield this._fileExists(fullPath))) { return res.status(404) .json({ message: `No such directory or file, '${path}'` }); } this._queue.add(() => __awaiter(this, void 0, void 0, function* () { return yield this._trashed({ path, bucket }); })); return res.ok(); } catch (err) { if (this._debug) { console.log(err); } return res.status(500).json({ message: err.message }); } }); this._apiConnect = ({ res, body }) => __awaiter(this, void 0, void 0, function* () { const { token, secret, bucket } = body; if (this._credentials != null) { const credentials = yield this._credentials({ token: String(token), secret: String(secret), bucket: String(bucket) }); if (!credentials) { return res.status(401).json({ message: 'Invalid credentials. Please check the your credentials' }); } } const directory = path_1.default.join(path_1.default.resolve(), this._normalizeDirectory({ bucket: String(bucket) })); if (!(yield this._fileExists(directory))) { fs_1.default.mkdirSync(directory, { recursive: true }); } return res.json({ accessToken: jsonwebtoken_1.default.sign({ data: { issuer: 'nfs-server', sub: { bucket, token } } }, this._jwtSecret, { expiresIn: this._jwtExipred, algorithm: 'HS256' }) }); }); this._studio = ({ res, cookies }) => __awaiter(this, void 0, void 0, function* () { const auth = cookies['auth.session']; if (!auth || auth == null) { const html = fs_1.default.readFileSync(path_1.default.join(__dirname, 'studio', 'login.html'), 'utf8'); return res.html(html); } const html = fs_1.default.readFileSync(path_1.default.join(__dirname, 'studio', 'index.html'), 'utf8'); const minifiedHtml = yield (0, html_minifier_terser_1.minify)(html, { collapseWhitespace: true, removeComments: true, minifyCSS: true, minifyJS: true }); return res.html(minifiedHtml); }); this._studioStorage = ({ req, res }) => __awaiter(this, void 0, void 0, function* () { var _b; const allowBuckets = (_b = req.buckets) !== null && _b !== void 0 ? _b : []; const rootFolder = this._rootFolder; const buckets = (this._buckets == null ? fs_1.default.readdirSync(path_1.default.join(path_1.default.resolve(), rootFolder)).filter((name) => { return fs_1.default.statSync(path_1.default.join(rootFolder, name)).isDirectory(); }) : yield this._buckets()).filter((bucket) => allowBuckets.includes(bucket) || allowBuckets[0] === '*'); const lists = []; for (const bucket of buckets) { const targetDir = `${rootFolder}/${bucket}`; const structures = this._fileStructure(targetDir); lists.push({ [bucket]: structures }); } const storageSync = (dirPath, buckets) => { let totalSize = 0; const readDirSync = (dir) => { const fullDirectory = path_1.default.join(path_1.default.resolve(), dir); const files = fs_1.default.readdirSync(fullDirectory, { withFileTypes: true }); for (const file of files) { const fullPath = path_1.default.join(path_1.default.resolve(), dir, file.name); const filePath = path_1.default.join(dir, file.name); const stats = fs_1.default.statSync(fullPath); if (stats.isDirectory()) { if (!buckets.includes(filePath.replace(/\\/g, '/').split('/')[1])) continue; readDirSync(filePath); continue; } totalSize += stats.size; } }; readDirSync(dirPath); return totalSize; }; const bytes = storageSync(this._rootFolder, buckets); return res.ok({ buckets: buckets.length, storage: { bytes, kb: Number((bytes / 1024).toFixed(2)), mb: Number((bytes / (1024 * 1024)).toFixed(2)), gb: Number((bytes / (1024 * 1024 * 1024)).toFixed(2)) } }); }); this._studioPreview = ({ req, res, params }) => __awaiter(this, void 0, void 0, function* () { var _c, _d; const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, ""); const [bucket, ...rest] = String(path).split('/'); const allowBuckets = req.buckets || []; if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) { return res.forbidden(); } let filePath = rest.join('/'); if (filePath != null) { filePath = this._normalizeFolder(String(filePath)); } const directory = this._normalizeDirectory({ bucket, folder: null }); if (!fs_1.default.existsSync(path_1.default.join(path_1.default.resolve(), directory, filePath))) { return res.notFound(`The directory '${path}' does not exist`); } const extension = path_1.default.extname(filePath).replace(/\./g, ''); const textFileExtensions = [ 'txt', 'md', 'csv', 'json', 'xml', 'html', 'css', 'js', 'ts', 'java', 'go', 'rs', 'py', 'log', 'yaml', 'ini', 'bat', 'sh', 'sql', 'conf', 'rtf', 'tex', 'srt', 'plist', 'env', 'yml', 'yaml', 'key' ]; if (textFileExtensions.includes(extension)) { const html = fs_1.default.readFileSync(path_1.default.join(__dirname, 'studio', 'vs-code.html'), 'utf8'); const minifiedHtml = yield (0, html_minifier_terser_1.minify)(html, { collapseWhitespace: true, removeComments: true, minifyCSS: true, minifyJS: true }); const language = (_c = { 'ts': 'typescript', 'js': 'javascript' }[extension]) !== null && _c !== void 0 ? _c : extension; return res.html(minifiedHtml.replace('{{language}}', language)); } const { stream, set } = yield this._makeStream({ bucket: bucket, filePath: String(filePath), range: (_d = req.headers) === null || _d === void 0 ? void 0 : _d.range, download: true }); set(res); return stream.pipe(res); }); this._stduioPreviewText = ({ req, res, params }) => __awaiter(this, void 0, void 0, function* () { const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, ""); const [bucket, ...rest] = String(path).split('/'); const allowBuckets = req.buckets || []; if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) { return res.forbidden(); } let filePath = rest.join('/'); if (filePath != null) { filePath = this._normalizeFolder(String(filePath)); } const directory = this._normalizeDirectory({ bucket, folder: null }); if (!fs_1.default.existsSync(path_1.default.join(path_1.default.resolve(), directory, filePath))) { return res.notFound(`The directory '${path}' does not exist`); } const text = fs_1.default.readFileSync(path_1.default.join(path_1.default.resolve(), this._rootFolder, bucket, filePath), 'utf8'); return res.send(text); }); this._stduioPreviewTextEdit = ({ req, res, params, body }) => __awaiter(this, void 0, void 0, function* () { if (body.content == null) { return res.badRequest('Please enter a content for rewrite file'); } const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, ""); const [bucket, ...rest] = String(path).split('/'); const allowBuckets = req.buckets || []; if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) { return res.forbidden(); } let filePath = rest.join('/'); if (filePath != null) { filePath = this._normalizeFolder(String(filePath)); } const directory = this._normalizeDirectory({ bucket, folder: null }); const fullPath = path_1.default.join(path_1.default.resolve(), directory, filePath); if (!fs_1.default.existsSync(fullPath)) { return res.notFound(`The directory '${path}' does not exist`); } fs_1.default.writeFileSync(fullPath, String(body.content), 'utf-8'); return res.ok(); }); this._studioLogin = ({ res, body }) => __awaiter(this, void 0, void 0, function* () { var _e; if (this._onStudioCredentials == null) { return res.badRequest('Please enable the studio'); } const { username, password } = body; if (!username) { return res.badRequest('Please enter an username'); } const check = yield this._onStudioCredentials({ username: String(username), password: String(password) }); if (!(check === null || check === void 0 ? void 0 : check.logged)) return res.unauthorized('Please check your username and password'); const EXPIRED = 43200; const session = jsonwebtoken_1.default.sign({ data: { issuer: 'nfs-studio', sub: { buckets: (_e = check === null || check === void 0 ? void 0 : check.buckets) !== null && _e !== void 0 ? _e : [], permissions: ['*'], token: Buffer.from(`${+new Date()}`).toString('base64') } } }, this._jwtSecret, { expiresIn: EXPIRED, algorithm: 'HS256' }); res.setHeader('Set-Cookie', `auth.session=${session}; HttpOnly; Max-Age=${EXPIRED}; Path=/studio`); return res.ok(); }); this._studioLogout = ({ res }) => __awaiter(this, void 0, void 0, function* () { res.setHeader('Set-Cookie', `auth.session=; HttpOnly; Max-Age=0; Path=/studio`); return res.ok(); }); this._studioUpload = ({ req, res, files, body }) => __awaiter(this, void 0, void 0, function* () { try { if (!Array.isArray(files === null || files === void 0 ? void 0 : files.file) || (files === null || files === void 0 ? void 0 : files.file[0]) == null) { return res.badRequest('The file is required.'); } if (body.path == null || body.path === '') { return res.badRequest('The path is required.'); } const file = files === null || files === void 0 ? void 0 : files.file[0]; const [bucket, ...rest] = String(body.path).split('/'); const allowBuckets = req.buckets || []; if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) { return res.forbidden(); } if (this._buckets != null && !(yield this._buckets()).includes(bucket)) { return res.forbidden(); } let folder = rest.join('/'); if (folder != null) { folder = this._normalizeFolder(String(folder)); } const directory = this._normalizeDirectory({ bucket, folder }); if (!(yield this._fileExists(directory))) { if (this._debug) { console.log({ directory, bucket, folder }); } fs_1.default.mkdirSync(directory, { recursive: true }); } const writeFile = (file, to) => { return new Promise((resolve, reject) => { fs_1.default.createReadStream(file) .pipe(fs_1.default.createWriteStream(to)) .on('finish', () => { // remove temporary from server this._remove(file, { delayMs: 0 }); return resolve(null); }) .on('error', (err) => reject(err)); return; }); }; const name = `${file.name}`; yield writeFile(file.tempFilePath, this._normalizePath({ directory, path: name, full: true })); return res.ok({ path: this._normalizePath({ directory: folder, path: name }), name: name, size: file.size }); } catch (err) { if (this._debug) { console.log(err); } throw err; } }); this._studioBucket = ({ req, res }) => __awaiter(this, void 0, void 0, function* () { var _f; const allowBuckets = (_f = req.buckets) !== null && _f !== void 0 ? _f : []; const rootFolder = this._rootFolder; const buckets = (this._buckets == null ? fs_1.default.readdirSync(path_1.default.join(path_1.default.resolve(), rootFolder)).filter((name) => { return fs_1.default.statSync(path_1.default.join(rootFolder, name)).isDirectory(); }) : yield this._buckets()).filter((bucket) => allowBuckets.includes(bucket) || allowBuckets[0] === '*'); const lists = []; const storageSync = (dirPath, bucket) => { let totalSize = 0; const readDirSync = (dir) => { const fullDirectory = path_1.default.join(path_1.default.resolve(), dir); const files = fs_1.default.readdirSync(fullDirectory, { withFileTypes: true }); for (const file of files) { const fullPath = path_1.default.join(path_1.default.resolve(), dir, file.name); const filePath = path_1.default.join(dir, file.name); const stats = fs_1.default.statSync(fullPath); if (stats.isDirectory()) { readDirSync(filePath); continue; } totalSize += stats.size; } }; readDirSync([dirPath, bucket].join('/')); return totalSize; }; for (const bucket of buckets) { if (allowBuckets.includes(bucket) || allowBuckets[0] === '*') { const fullPath = path_1.default.join(path_1.default.resolve(), this._rootFolder, bucket); if (!(yield this._fileExists(fullPath))) { fs_1.default.mkdirSync(fullPath, { recursive: true }); if (this._onStudioBucketCreated != null) { const random = () => [1, 2, 3].map(v => Math.random().toString(36).substring(3)).join(''); yield this._onStudioBucketCreated({ bucket: String(bucket), token: String(random()), secret: String(random()) }); } } const loadCredentials = this._onLoadBucketCredentials == null ? [] : yield this._onLoadBucketCredentials(); const credentials = loadCredentials.find(v => v.bucket === bucket); const bytes = storageSync(this._rootFolder, bucket); lists.push({ [bucket]: { credentials, storage: { bytes, kb: Number((bytes / 1024).toFixed(2)), mb: Number((bytes / (1024 * 1024)).toFixed(2)), gb: Number((bytes / (1024 * 1024 * 1024)).toFixed(2)) } } }); } } return res.ok({ buckets: lists }); }); this._studioBucketCreate = ({ req, res, body }) => __awaiter(this, void 0, void 0, function* () { const { bucket, token, secret } = body; if ([bucket, token, secret].some(v => v == null || v === '')) { return res.badRequest('Please enter a bucket token and secret'); } const directory = this._normalizeDirectory({ bucket: String(bucket), folder: null }); if (!(yield this._fileExists(directory))) { fs_1.default.mkdirSync(directory, { recursive: true }); } if (this._onStudioBucketCreated != null) { yield this._onStudioBucketCreated({ bucket: String(bucket), token: String(token), secret: String(secret) }); } return res.ok(); }); this._studioFiles = ({ req, res, params }) => __awaiter(this, void 0, void 0, function* () { const rootFolder = this._rootFolder; const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, ""); const [bucket] = String(path).split('/'); const allowBuckets = req.buckets || []; if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) { return res.forbidden(); } const targetDir = `${rootFolder}/${path}`; if (!fs_1.default.existsSync(path_1.default.join(path_1.default.resolve(), targetDir))) { return res.notFound(`The directory '${path}' does not exist`); } const files = this._fileStructure(targetDir, { includeFiles: true }); return res.ok({ files: files.sort((a, b) => { if (a.isFolder !== b.isFolder) { return b.isFolder - a.isFolder; } return +new Date(a.lastModified) - +new Date(b.lastModified); }) }); }); this._studioEdit = ({ req, res, params, body }) => __awaiter(this, void 0, void 0, function* () { const path = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, ""); const [bucket, ...rest] = String(path).split('/'); const allowBuckets = req.buckets || []; if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) { return res.forbidden(); } const { rename } = body; if (rename == null || rename === '') { return res.badRequest('Please enter the name you wish to use.'); } let filePath = rest.join('/'); if (filePath != null) { filePath = this._normalizeFolder(String(filePath)); } const oldPath = this._normalizeDirectory({ bucket, folder: filePath }); if (!fs_1.default.existsSync(path_1.default.join(path_1.default.resolve(), oldPath))) return res.notFound(); const newPath = this._normalizeDirectory({ bucket, folder: `${path_1.default.dirname(filePath)}/${rename}${path_1.default.extname(filePath)}` }); fs_1.default.renameSync(path_1.default.join(path_1.default.resolve(), oldPath), path_1.default.join(path_1.default.resolve(), newPath)); return res.ok({ name: rename }); }); this._studioRemove = ({ req, res, body, params }) => __awaiter(this, void 0, void 0, function* () { const data = String(params['*']).replace(/^\/+/, '').replace(/\.{2}(?!\.)/g, ""); const [bucket, ...rest] = String(data).split('/'); const path = rest.join('/'); const allowBuckets = req.buckets || []; if (!(allowBuckets.includes(bucket) || allowBuckets[0] === '*')) { return res.forbidden(); } const filePath = this._normalizeFolder(String(path)); const fullPath = path_1.default.join(path_1.default.resolve(), this._normalizeDirectory({ bucket, folder: filePath })); if (!fs_1.default.existsSync(fullPath)) return res.notFound(); const stats = fs_1.default.statSync(fullPath); if (stats.isDirectory()) { if (path.includes(this._trash)) { fs_1.default.rmSync(fullPath, { recursive: true, force: true }); return res.ok(); } this._queue.add(() => __awaiter(this, void 0, void 0, function* () { return yield this._trashedWithFolder({ path, bucket }); })); return res.ok(); } if (path.includes(this._trash)) { this._remove(fullPath, { delayMs: 0 }); return res.ok(); } this._queue.add(() => __awaiter(this, void 0, void 0, function* () { return yield this._trashed({ path, bucket }); })); return res.ok(); }); this._fileStructure = (dirPath, { includeFiles = false } = {}) => { const items = []; const files = fs_1.default.readdirSync(dirPath); for (const file of files) { const path = path_1.default.join(dirPath, file); const fullPath = path_1.default.join(path_1.default.resolve(), dirPath, file); const stats = fs_1.default.lstatSync(fullPath); const lastModified = stats.mtime; if (stats.isDirectory()) { items.push({ name: file, path: path.replace(/\\/g, '/').replace(`${this._rootFolder}/`, ''), isFolder: true, lastModified, folders: this._fileStructure(path, { includeFiles }) }); continue; } if (!includeFiles) continue; const extension = path_1.default.extname(file).replace(/\./g, ''); items.push({ name: file, path: path.replace(/\\/g, '/').replace(this._rootFolder, ''), isFolder: false, lastModified, size: stats.size, extension }); } return items; }; this._authMiddleware = ({ req, res, headers }, next) => { const authorization = String(headers.authorization).split(' ')[1]; if (authorization == null) { return res.status(401).json({ message: 'Please check your credentials. Are they valid ?' }); } const { bucket, token } = this._verify(authorization); req.bucket = bucket; req.token = token; return next(); }; this._authStudioMiddleware = ({ req, res, cookies }, next) => { var _a, _b; const authorization = cookies['auth.session']; if (authorization == null || authorization === '') { if ((_a = req.url) === null || _a === void 0 ? void 0 : _a.includes('/studio/preview')) { res.writeHead(401, { 'Content-Type': 'text/xml' }); const error = { Error: [ { Code: 'Unauthorized' }, { Message: 'Please check your credentials. Are they valid ?' }, { Resource: req.url }, { RequestKey: "" } ] }; return res.end((0, xml_1.default)([error], { declaration: true })); } return res.status(401).json({ message: 'Please check your credentials. Are they valid ?' }); } try { const { buckets, token } = this._verify(authorization); req.buckets = buckets; req.token = token; return next(); } catch (e) { if ((_b = req.url) === null || _b === void 0 ? void 0 : _b.includes('/studio/preview')) { res.writeHead(400, { 'Content-Type': 'text/xml' }); const error = { Error: [ { Code: 'Bad Request' }, { Message: e.message }, { Resource: req.url }, { RequestKey: "" } ] }; return res.end((0, xml_1.default)([error], { declaration: true })); } return res.status(400).json({ message: e.message }); } }; this._removeOldDirInTrash = (bucket) => __awaiter(this, void 0, void 0, function* () { const directory = this._normalizeDirectory({ bucket, folder: this._trash }); const files = yield fs_1.default.promises.readdir(directory); for (const file of files) { const dir = this._normalizePath({ directory, path: file, full: true }); const stats = yield fs_1.default.promises.stat(dir); if (!stats.isDirectory()) continue; const format = file.match(/^\d{4}-\d{2}-\d{2}/); const folderDate = new tspace_utils_1.Time(format ? format[0] : 0).toTimestamp(); const ago = new tspace_utils_1.Time().minusDays(this._backup).toTimeStamp(); if (Number.isNaN(folderDate) || folderDate > ago) continue; yield this._removeDir(dir); } }); this._removeDir = (path) => __awaiter(this, void 0, void 0, function* () { return yield fs_1.default.promises .rm(path, { recursive: true }) .catch(_ => { return; }); }); } get instance() { return this._app; } /** * The 'progress' is method used to view the progress of the file upload. * * @returns {this} */ debug() { this._debug = true; return this; } /** * The 'progress' is method used to view the progress of the file upload. * * @returns {this} */ progress() { this._progress = true; return this; } /** * The