Prefer sanitize filename with extension

1e3fada8d7f6b6e8b728ca6e12d31ec990b78644

Cohee <18619528+Cohee1207@users.noreply.github.com>

9 files changed, +24 -22Ignore whitespace
public/scripts/power-user.js+3 -3
@@ -44,7 +44,7 @@ import { tokenizers } from './tokenizers.js';
44import { BIAS_CACHE } from './logit-bias.js';44import { BIAS_CACHE } from './logit-bias.js';
45import { renderTemplateAsync } from './templates.js';45import { renderTemplateAsync } from './templates.js';
4646
47import { countOccurrences, debounce, delay, download, getFileText, getStringHash, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';47import { countOccurrences, debounce, delay, download, getFileText, getSanitizedFilename, getStringHash, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
48import { FILTER_TYPES } from './filters.js';48import { FILTER_TYPES } from './filters.js';
49import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';49import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
50import { SlashCommand } from './slash-commands/SlashCommand.js';50import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -2347,7 +2347,7 @@ async function saveTheme(name = undefined, theme = undefined) {
2347 return;2347 return;
2348 }2348 }
23492349
2350 name = String(newName);2350 name = await getSanitizedFilename(String(newName));
2351 }2351 }
23522352
2353 if (typeof theme !== 'object') {2353 if (typeof theme !== 'object') {
@@ -2458,7 +2458,7 @@ async function saveMovingUI() {
2458 return;2458 return;
2459 }2459 }
24602460
2461 const name = String(popupResult);2461 const name = await getSanitizedFilename(String(popupResult));
24622462
2463 const movingUIPreset = {2463 const movingUIPreset = {
2464 name,2464 name,
src/endpoints/groups.js+6 -5
@@ -6,6 +6,7 @@ import sanitize from 'sanitize-filename';
6import { sync as writeFileAtomicSync } from 'write-file-atomic';6import { sync as writeFileAtomicSync } from 'write-file-atomic';
77
8import { humanizedISO8601DateTime } from '../util.js';8import { humanizedISO8601DateTime } from '../util.js';
9import { getFileNameValidationFunction } from '../middleware/validateFileName.js';
910
10export const router = express.Router();11export const router = express.Router();
1112
@@ -76,7 +77,7 @@ router.post('/create', (request, response) => {
76 generation_mode_join_prefix: request.body.generation_mode_join_prefix ?? '',77 generation_mode_join_prefix: request.body.generation_mode_join_prefix ?? '',
77 generation_mode_join_suffix: request.body.generation_mode_join_suffix ?? '',78 generation_mode_join_suffix: request.body.generation_mode_join_suffix ?? '',
78 };79 };
79 const pathToFile = path.join(request.user.directories.groups, `${id}.json`);80 const pathToFile = path.join(request.user.directories.groups, sanitize(`${id}.json`));
80 const fileData = JSON.stringify(groupMetadata, null, 4);81 const fileData = JSON.stringify(groupMetadata, null, 4);
8182
82 if (!fs.existsSync(request.user.directories.groups)) {83 if (!fs.existsSync(request.user.directories.groups)) {
@@ -87,19 +88,19 @@ router.post('/create', (request, response) => {
87 return response.send(groupMetadata);88 return response.send(groupMetadata);
88});89});
8990
90router.post('/edit', (request, response) => {91router.post('/edit', getFileNameValidationFunction('id'), (request, response) => {
91 if (!request.body || !request.body.id) {92 if (!request.body || !request.body.id) {
92 return response.sendStatus(400);93 return response.sendStatus(400);
93 }94 }
94 const id = request.body.id;95 const id = request.body.id;
95 const pathToFile = path.join(request.user.directories.groups, `${id}.json`);96 const pathToFile = path.join(request.user.directories.groups, sanitize(`${id}.json`));
96 const fileData = JSON.stringify(request.body, null, 4);97 const fileData = JSON.stringify(request.body, null, 4);
9798
98 writeFileAtomicSync(pathToFile, fileData);99 writeFileAtomicSync(pathToFile, fileData);
99 return response.send({ ok: true });100 return response.send({ ok: true });
100});101});
101102
102router.post('/delete', async (request, response) => {103router.post('/delete', getFileNameValidationFunction('id'), async (request, response) => {
103 if (!request.body || !request.body.id) {104 if (!request.body || !request.body.id) {
104 return response.sendStatus(400);105 return response.sendStatus(400);
105 }106 }
@@ -114,7 +115,7 @@ router.post('/delete', async (request, response) => {
114 if (group && Array.isArray(group.chats)) {115 if (group && Array.isArray(group.chats)) {
115 for (const chat of group.chats) {116 for (const chat of group.chats) {
116 console.info('Deleting group chat', chat);117 console.info('Deleting group chat', chat);
117 const pathToFile = path.join(request.user.directories.groupChats, `${id}.jsonl`);118 const pathToFile = path.join(request.user.directories.groupChats, sanitize(`${chat}.jsonl`));
118119
119 if (fs.existsSync(pathToFile)) {120 if (fs.existsSync(pathToFile)) {
120 fs.unlinkSync(pathToFile);121 fs.unlinkSync(pathToFile);
src/endpoints/moving-ui.js+1 -1
@@ -10,7 +10,7 @@ router.post('/save', (request, response) => {
10 return response.sendStatus(400);10 return response.sendStatus(400);
11 }11 }
1212
13 const filename = path.join(request.user.directories.movingUI, sanitize(request.body.name) + '.json');13 const filename = path.join(request.user.directories.movingUI, sanitize(`${request.body.name}.json`));
14 writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');14 writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
1515
16 return response.sendStatus(200);16 return response.sendStatus(200);
src/endpoints/presets.js+4 -4
@@ -11,7 +11,7 @@ import { getDefaultPresetFile, getDefaultPresets } from './content-manager.js';
11 * Gets the folder and extension for the preset settings based on the API source ID.11 * Gets the folder and extension for the preset settings based on the API source ID.
12 * @param {string} apiId API source ID12 * @param {string} apiId API source ID
13 * @param {import('../users.js').UserDirectoryList} directories User directories13 * @param {import('../users.js').UserDirectoryList} directories User directories
14 * @returns {object} Object containing the folder and extension for the preset settings14 * @returns {{folder: string?, extension: string?}} Object containing the folder and extension for the preset settings
15 */15 */
16function getPresetSettingsByAPI(apiId, directories) {16function getPresetSettingsByAPI(apiId, directories) {
17 switch (apiId) {17 switch (apiId) {
@@ -108,8 +108,7 @@ router.post('/save-openai', function (request, response) {
108 const name = sanitize(request.query.name);108 const name = sanitize(request.query.name);
109 if (!name) return response.sendStatus(400);109 if (!name) return response.sendStatus(400);
110110
111 const filename = `${name}.json`;111 const fullpath = path.join(request.user.directories.openAI_Settings, `${name}.json`);
112 const fullpath = path.join(request.user.directories.openAI_Settings, filename);
113 writeFileAtomicSync(fullpath, JSON.stringify(request.body, null, 4), 'utf-8');112 writeFileAtomicSync(fullpath, JSON.stringify(request.body, null, 4), 'utf-8');
114 return response.send({ name });113 return response.send({ name });
115});114});
@@ -120,7 +119,8 @@ router.post('/delete-openai', function (request, response) {
120 return response.sendStatus(400);119 return response.sendStatus(400);
121 }120 }
122121
123 const name = request.body.name;122 const name = sanitize(request.body.name);
123 if (!name) return response.sendStatus(400);
124 const pathToFile = path.join(request.user.directories.openAI_Settings, `${name}.json`);124 const pathToFile = path.join(request.user.directories.openAI_Settings, `${name}.json`);
125125
126 if (fs.existsSync(pathToFile)) {126 if (fs.existsSync(pathToFile)) {
src/endpoints/quick-replies.js+2 -2
@@ -12,7 +12,7 @@ router.post('/save', (request, response) => {
12 return response.sendStatus(400);12 return response.sendStatus(400);
13 }13 }
1414
15 const filename = path.join(request.user.directories.quickreplies, sanitize(request.body.name) + '.json');15 const filename = path.join(request.user.directories.quickreplies, sanitize(`${request.body.name}.json`));
16 writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');16 writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
1717
18 return response.sendStatus(200);18 return response.sendStatus(200);
@@ -23,7 +23,7 @@ router.post('/delete', (request, response) => {
23 return response.sendStatus(400);23 return response.sendStatus(400);
24 }24 }
2525
26 const filename = path.join(request.user.directories.quickreplies, sanitize(request.body.name) + '.json');26 const filename = path.join(request.user.directories.quickreplies, sanitize(`${request.body.name}.json`));
27 if (fs.existsSync(filename)) {27 if (fs.existsSync(filename)) {
28 fs.unlinkSync(filename);28 fs.unlinkSync(filename);
29 }29 }
src/endpoints/themes.js+3 -3
@@ -12,19 +12,19 @@ router.post('/save', (request, response) => {
12 return response.sendStatus(400);12 return response.sendStatus(400);
13 }13 }
1414
15 const filename = path.join(request.user.directories.themes, sanitize(request.body.name) + '.json');15 const filename = path.join(request.user.directories.themes, sanitize(`${request.body.name}.json`));
16 writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');16 writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
1717
18 return response.sendStatus(200);18 return response.sendStatus(200);
19});19});
2020
21router.post('/delete', function (request, response) {21router.post('/delete', (request, response) => {
22 if (!request.body || !request.body.name) {22 if (!request.body || !request.body.name) {
23 return response.sendStatus(400);23 return response.sendStatus(400);
24 }24 }
2525
26 try {26 try {
27 const filename = path.join(request.user.directories.themes, sanitize(request.body.name) + '.json');27 const filename = path.join(request.user.directories.themes, sanitize(`${request.body.name}.json`));
28 if (!fs.existsSync(filename)) {28 if (!fs.existsSync(filename)) {
29 console.error('Theme file not found:', filename);29 console.error('Theme file not found:', filename);
30 return response.sendStatus(404);30 return response.sendStatus(404);
src/endpoints/thumbnails.js+1 -1
@@ -83,7 +83,7 @@ export function invalidateThumbnail(directories, type, file) {
83 const folder = getThumbnailFolder(directories, type);83 const folder = getThumbnailFolder(directories, type);
84 if (folder === undefined) throw new Error('Invalid thumbnail type');84 if (folder === undefined) throw new Error('Invalid thumbnail type');
8585
86 const pathToThumbnail = path.join(folder, file);86 const pathToThumbnail = path.join(folder, sanitize(file));
8787
88 if (fs.existsSync(pathToThumbnail)) {88 if (fs.existsSync(pathToThumbnail)) {
89 fs.unlinkSync(pathToThumbnail);89 fs.unlinkSync(pathToThumbnail);
src/endpoints/worldinfo.js+2 -2
@@ -19,7 +19,7 @@ export function readWorldInfoFile(directories, worldInfoName, allowDummy) {
19 return dummyObject;19 return dummyObject;
20 }20 }
2121
22 const filename = `${worldInfoName}.json`;22 const filename = sanitize(`${worldInfoName}.json`);
23 const pathToWorldInfo = path.join(directories.worlds, filename);23 const pathToWorldInfo = path.join(directories.worlds, filename);
2424
25 if (!fs.existsSync(pathToWorldInfo)) {25 if (!fs.existsSync(pathToWorldInfo)) {
@@ -114,7 +114,7 @@ router.post('/edit', (request, response) => {
114 return response.status(400).send('Is not a valid world info file');114 return response.status(400).send('Is not a valid world info file');
115 }115 }
116116
117 const filename = `${sanitize(request.body.name)}.json`;117 const filename = sanitize(`${request.body.name}.json`);
118 const pathToFile = path.join(request.user.directories.worlds, filename);118 const pathToFile = path.join(request.user.directories.worlds, filename);
119119
120 writeFileAtomicSync(pathToFile, JSON.stringify(request.body.data, null, 4));120 writeFileAtomicSync(pathToFile, JSON.stringify(request.body.data, null, 4));
src/users.js+2 -1
@@ -13,6 +13,7 @@ import mime from 'mime-types';
13import archiver from 'archiver';13import archiver from 'archiver';
14import _ from 'lodash';14import _ from 'lodash';
15import { sync as writeFileAtomicSync } from 'write-file-atomic';15import { sync as writeFileAtomicSync } from 'write-file-atomic';
16import sanitize from 'sanitize-filename';
1617
17import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE, UPLOADS_DIRECTORY } from './constants.js';18import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE, UPLOADS_DIRECTORY } from './constants.js';
18import { getConfigValue, color, delay, generateTimestamp } from './util.js';19import { getConfigValue, color, delay, generateTimestamp } from './util.js';
@@ -670,7 +671,7 @@ export async function getUserAvatar(handle) {
670 if (!avatarFile) {671 if (!avatarFile) {
671 return PUBLIC_USER_AVATAR;672 return PUBLIC_USER_AVATAR;
672 }673 }
673 const avatarPath = path.join(directory.avatars, avatarFile);674 const avatarPath = path.join(directory.avatars, sanitize(avatarFile));
674 if (!fs.existsSync(avatarPath)) {675 if (!fs.existsSync(avatarPath)) {
675 return PUBLIC_USER_AVATAR;676 return PUBLIC_USER_AVATAR;
676 }677 }