Fix absolute paths for data root. Allow setting data root via console args.

This commit is contained in:
Cohee 2024-04-12 19:53:46 +03:00
parent 3e1ff9bc25
commit dcbeab0aef
6 changed files with 112 additions and 56 deletions

View File

@ -102,6 +102,10 @@ const cliArguments = yargs(hideBin(process.argv))
type: 'boolean',
default: false,
describe: 'Enables whitelist mode',
}).option('dataRoot', {
type: 'string',
default: null,
describe: 'Root directory for data storage',
}).parseSync();
// change all relative paths
@ -121,6 +125,7 @@ const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTOR
const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);
const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
const basicAuthMode = getConfigValue('basicAuthMode', false);
const enableAccounts = getConfigValue('enableUserAccounts', false);
@ -526,7 +531,7 @@ const setupTasks = async function () {
// TODO: do endpoint init functions depend on certain directories existing or not existing? They should be callable
// in any order for encapsulation reasons, but right now it's unknown if that would break anything.
await userModule.initUserStorage();
await userModule.initUserStorage(dataRoot);
await settingsEndpoint.init();
const directories = await userModule.ensurePublicDirectoriesExist();
await userModule.migrateUserData();

View File

@ -1,5 +1,6 @@
const path = require('path');
const fs = require('fs');
const mime = require('mime-types');
const express = require('express');
const sanitize = require('sanitize-filename');
const fetch = require('node-fetch').default;
@ -216,9 +217,11 @@ router.post('/download', jsonParser, async (request, response) => {
await finished(res.body.pipe(fileStream));
if (category === 'character') {
response.sendFile(temp_path, { root: process.cwd() }, () => {
fs.rmSync(temp_path);
});
const fileContent = fs.readFileSync(temp_path);
const contentType = mime.lookup(temp_path) || 'application/octet-stream';
response.setHeader('Content-Type', contentType);
response.send(fileContent);
fs.rmSync(temp_path);
return;
}

View File

@ -1,11 +1,13 @@
const path = require('path');
const fs = require('fs');
const fsPromises = require('fs').promises;
const readline = require('readline');
const express = require('express');
const sanitize = require('sanitize-filename');
const writeFileAtomicSync = require('write-file-atomic').sync;
const yaml = require('yaml');
const _ = require('lodash');
const mime = require('mime-types');
const jimp = require('jimp');
@ -1078,33 +1080,43 @@ router.post('/duplicate', jsonParser, async function (request, response) {
});
router.post('/export', jsonParser, async function (request, response) {
if (!request.body.format || !request.body.avatar_url) {
return response.sendStatus(400);
}
try {
if (!request.body.format || !request.body.avatar_url) {
return response.sendStatus(400);
}
let filename = path.join(request.user.directories.characters, sanitize(request.body.avatar_url));
let filename = path.join(request.user.directories.characters, sanitize(request.body.avatar_url));
if (!fs.existsSync(filename)) {
return response.sendStatus(404);
}
if (!fs.existsSync(filename)) {
return response.sendStatus(404);
}
switch (request.body.format) {
case 'png':
return response.sendFile(filename, { root: process.cwd() });
case 'json': {
try {
let json = await readCharacterData(filename);
if (json === undefined) return response.sendStatus(400);
let jsonObject = getCharaCardV2(JSON.parse(json), request.user.directories);
return response.type('json').send(JSON.stringify(jsonObject, null, 4));
switch (request.body.format) {
case 'png': {
const fileContent = await fsPromises.readFile(filename);
const contentType = mime.lookup(filename) || 'image/png';
response.setHeader('Content-Type', contentType);
response.setHeader('Content-Disposition', `attachment; filename=${path.basename(filename)}`);
return response.send(fileContent);
}
catch {
return response.sendStatus(400);
case 'json': {
try {
let json = await readCharacterData(filename);
if (json === undefined) return response.sendStatus(400);
let jsonObject = getCharaCardV2(JSON.parse(json), request.user.directories);
return response.type('json').send(JSON.stringify(jsonObject, null, 4));
}
catch {
return response.sendStatus(400);
}
}
}
}
return response.sendStatus(400);
return response.sendStatus(400);
} catch (err) {
console.error('Character export failed', err);
response.sendStatus(500);
}
});
module.exports = { router };

View File

@ -122,7 +122,7 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
}
const basePath = path.parse(contentItem.filename).base;
const targetPath = path.join(process.cwd(), contentTarget, basePath);
const targetPath = path.join(contentTarget, basePath);
if (fs.existsSync(targetPath)) {
console.log(`Content file ${contentItem.filename} already exists in ${contentTarget}`);

View File

@ -1,5 +1,7 @@
const fs = require('fs');
const fsPromises = require('fs').promises;
const path = require('path');
const mime = require('mime-types');
const express = require('express');
const sanitize = require('sanitize-filename');
const jimp = require('jimp');
@ -165,38 +167,63 @@ const router = express.Router();
// Important: This route must be mounted as '/thumbnail'. It is used in the client code and saved to chat files.
router.get('/', jsonParser, async function (request, response) {
if (typeof request.query.file !== 'string' || typeof request.query.type !== 'string') return response.sendStatus(400);
try{
if (typeof request.query.file !== 'string' || typeof request.query.type !== 'string') {
return response.sendStatus(400);
}
const type = request.query.type;
const file = sanitize(request.query.file);
const type = request.query.type;
const file = sanitize(request.query.file);
if (!type || !file) {
return response.sendStatus(400);
if (!type || !file) {
return response.sendStatus(400);
}
if (!(type == 'bg' || type == 'avatar')) {
return response.sendStatus(400);
}
if (sanitize(file) !== file) {
console.error('Malicious filename prevented');
return response.sendStatus(403);
}
const thumbnailsDisabled = getConfigValue('disableThumbnails', false);
if (thumbnailsDisabled) {
const folder = getOriginalFolder(request.user.directories, type);
if (folder === undefined) {
return response.sendStatus(400);
}
const pathToOriginalFile = path.join(folder, file);
if (!fs.existsSync(pathToOriginalFile)) {
return response.sendStatus(404);
}
const contentType = mime.lookup(pathToOriginalFile) || 'image/png';
const originalFile = await fsPromises.readFile(pathToOriginalFile);
response.setHeader('Content-Type', contentType);
return response.send(originalFile);
}
const pathToCachedFile = await generateThumbnail(request.user.directories, type, file);
if (!pathToCachedFile) {
return response.sendStatus(404);
}
if (!fs.existsSync(pathToCachedFile)) {
return response.sendStatus(404);
}
const contentType = mime.lookup(pathToCachedFile) || 'image/jpeg';
const cachedFile = await fsPromises.readFile(pathToCachedFile);
response.setHeader('Content-Type', contentType);
return response.send(cachedFile);
} catch (error) {
console.error('Failed getting thumbnail', error);
return response.sendStatus(500);
}
if (!(type == 'bg' || type == 'avatar')) {
return response.sendStatus(400);
}
if (sanitize(file) !== file) {
console.error('Malicious filename prevented');
return response.sendStatus(403);
}
if (getConfigValue('disableThumbnails', false) == true) {
let folder = getOriginalFolder(request.user.directories, type);
if (folder === undefined) return response.sendStatus(400);
const pathToOriginalFile = path.join(folder, file);
return response.sendFile(pathToOriginalFile, { root: process.cwd() });
}
const pathToCachedFile = await generateThumbnail(request.user.directories, type, file);
if (!pathToCachedFile) {
return response.sendStatus(404);
}
return response.sendFile(pathToCachedFile, { root: process.cwd() });
});
module.exports = {

View File

@ -15,10 +15,15 @@ const { getConfigValue, color, delay, setConfigValue, generateTimestamp } = requ
const { readSecret, writeSecret } = require('./endpoints/secrets');
const KEY_PREFIX = 'user:';
const DATA_ROOT = getConfigValue('dataRoot', './data');
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
/**
* The root directory for user data.
* @type {string}
*/
let DATA_ROOT = './data';
/**
* Cache for user directories.
* @type {Map<string, UserDirectoryList>}
@ -312,9 +317,13 @@ function toKey(handle) {
/**
* Initializes the user storage. Currently a no-op.
* @param {string} dataRoot The root directory for user data
* @returns {Promise<void>}
*/
async function initUserStorage() {
async function initUserStorage(dataRoot) {
DATA_ROOT = dataRoot;
console.log('Using data root:', color.green(DATA_ROOT));
console.log();
await storage.init({
dir: path.join(DATA_ROOT, '_storage'),
ttl: true,