Prefer sanitize filename with extension

1e3fada8d7f6b6e8b728ca6e12d31ec990b78644

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

9 files changed, +24 -22Showing whitespace changes
public/scripts/power-user.js+3 -3
@@ -44,7 +44,7 @@ import { tokenizers } from './tokenizers.js';
4444import { BIAS_CACHE } from './logit-bias.js';
4545import { renderTemplateAsync } from './templates.js';
4646
4747import { countOccurrences, debounce, delay, download, getFileText, getSanitizedFilename, getStringHash, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
4848import { FILTER_TYPES } from './filters.js';
4949import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
5050import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -2347,7 +2347,7 @@ async function saveTheme(name = undefined, theme = undefined) {
23472347 return;
23482348 }
23492349
23502350 name = await getSanitizedFilename(String(newName));
23512351 }
23522352
23532353 if (typeof theme !== 'object') {
@@ -2458,7 +2458,7 @@ async function saveMovingUI() {
24582458 return;
24592459 }
24602460
24612461 const name = await getSanitizedFilename(String(popupResult));
24622462
24632463 const movingUIPreset = {
24642464 name,
src/endpoints/groups.js+6 -5
@@ -6,6 +6,7 @@ import sanitize from 'sanitize-filename';
66import { sync as writeFileAtomicSync } from 'write-file-atomic';
77
88import { humanizedISO8601DateTime } from '../util.js';
9+import { getFileNameValidationFunction } from '../middleware/validateFileName.js';
910
1011export const router = express.Router();
1112
@@ -76,7 +77,7 @@ router.post('/create', (request, response) => {
7677 generation_mode_join_prefix: request.body.generation_mode_join_prefix ?? '',
7778 generation_mode_join_suffix: request.body.generation_mode_join_suffix ?? '',
7879 };
7980 const pathToFile = path.join(request.user.directories.groups, sanitize(`${id}.json`));
8081 const fileData = JSON.stringify(groupMetadata, null, 4);
8182
8283 if (!fs.existsSync(request.user.directories.groups)) {
@@ -87,19 +88,19 @@ router.post('/create', (request, response) => {
8788 return response.send(groupMetadata);
8889});
8990
9091router.post('/edit', getFileNameValidationFunction('id'), (request, response) => {
9192 if (!request.body || !request.body.id) {
9293 return response.sendStatus(400);
9394 }
9495 const id = request.body.id;
9596 const pathToFile = path.join(request.user.directories.groups, sanitize(`${id}.json`));
9697 const fileData = JSON.stringify(request.body, null, 4);
9798
9899 writeFileAtomicSync(pathToFile, fileData);
99100 return response.send({ ok: true });
100101});
101102
102103router.post('/delete', getFileNameValidationFunction('id'), async (request, response) => {
103104 if (!request.body || !request.body.id) {
104105 return response.sendStatus(400);
105106 }
@@ -114,7 +115,7 @@ router.post('/delete', async (request, response) => {
114115 if (group && Array.isArray(group.chats)) {
115116 for (const chat of group.chats) {
116117 console.info('Deleting group chat', chat);
117118 const pathToFile = path.join(request.user.directories.groupChats, sanitize(`${idchat}.jsonl`));
118119
119120 if (fs.existsSync(pathToFile)) {
120121 fs.unlinkSync(pathToFile);
src/endpoints/moving-ui.js+1 -1
@@ -10,7 +10,7 @@ router.post('/save', (request, response) => {
1010 return response.sendStatus(400);
1111 }
1212
1313 const filename = path.join(request.user.directories.movingUI, sanitize(`${request.body.name) + '}.json'`));
1414 writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
1515
1616 return response.sendStatus(200);
src/endpoints/presets.js+4 -4
@@ -11,7 +11,7 @@ import { getDefaultPresetFile, getDefaultPresets } from './content-manager.js';
1111 * Gets the folder and extension for the preset settings based on the API source ID.
1212 * @param {string} apiId API source ID
1313 * @param {import('../users.js').UserDirectoryList} directories User directories
1414 * @returns {object{folder: string?, extension: string?}} Object containing the folder and extension for the preset settings
1515 */
1616function getPresetSettingsByAPI(apiId, directories) {
1717 switch (apiId) {
@@ -108,8 +108,7 @@ router.post('/save-openai', function (request, response) {
108108 const name = sanitize(request.query.name);
109109 if (!name) return response.sendStatus(400);
110110
111111 const filenamefullpath = path.join(request.user.directories.openAI_Settings, `${name}.json`);
112- const fullpath = path.join(request.user.directories.openAI_Settings, filename);
113112 writeFileAtomicSync(fullpath, JSON.stringify(request.body, null, 4), 'utf-8');
114113 return response.send({ name });
115114});
@@ -120,7 +119,8 @@ router.post('/delete-openai', function (request, response) {
120119 return response.sendStatus(400);
121120 }
122121
123122 const name = sanitize(request.body.name);
123+ if (!name) return response.sendStatus(400);
124124 const pathToFile = path.join(request.user.directories.openAI_Settings, `${name}.json`);
125125
126126 if (fs.existsSync(pathToFile)) {
src/endpoints/quick-replies.js+2 -2
@@ -12,7 +12,7 @@ router.post('/save', (request, response) => {
1212 return response.sendStatus(400);
1313 }
1414
1515 const filename = path.join(request.user.directories.quickreplies, sanitize(`${request.body.name) + '}.json'`));
1616 writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
1717
1818 return response.sendStatus(200);
@@ -23,7 +23,7 @@ router.post('/delete', (request, response) => {
2323 return response.sendStatus(400);
2424 }
2525
2626 const filename = path.join(request.user.directories.quickreplies, sanitize(`${request.body.name) + '}.json'`));
2727 if (fs.existsSync(filename)) {
2828 fs.unlinkSync(filename);
2929 }
src/endpoints/themes.js+3 -3
@@ -12,19 +12,19 @@ router.post('/save', (request, response) => {
1212 return response.sendStatus(400);
1313 }
1414
1515 const filename = path.join(request.user.directories.themes, sanitize(`${request.body.name) + '}.json'`));
1616 writeFileAtomicSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
1717
1818 return response.sendStatus(200);
1919});
2020
2121router.post('/delete', function (request, response) => {
2222 if (!request.body || !request.body.name) {
2323 return response.sendStatus(400);
2424 }
2525
2626 try {
2727 const filename = path.join(request.user.directories.themes, sanitize(`${request.body.name) + '}.json'`));
2828 if (!fs.existsSync(filename)) {
2929 console.error('Theme file not found:', filename);
3030 return response.sendStatus(404);
src/endpoints/thumbnails.js+1 -1
@@ -83,7 +83,7 @@ export function invalidateThumbnail(directories, type, file) {
8383 const folder = getThumbnailFolder(directories, type);
8484 if (folder === undefined) throw new Error('Invalid thumbnail type');
8585
8686 const pathToThumbnail = path.join(folder, sanitize(file));
8787
8888 if (fs.existsSync(pathToThumbnail)) {
8989 fs.unlinkSync(pathToThumbnail);
src/endpoints/worldinfo.js+2 -2
@@ -19,7 +19,7 @@ export function readWorldInfoFile(directories, worldInfoName, allowDummy) {
1919 return dummyObject;
2020 }
2121
2222 const filename = sanitize(`${worldInfoName}.json`);
2323 const pathToWorldInfo = path.join(directories.worlds, filename);
2424
2525 if (!fs.existsSync(pathToWorldInfo)) {
@@ -114,7 +114,7 @@ router.post('/edit', (request, response) => {
114114 return response.status(400).send('Is not a valid world info file');
115115 }
116116
117117 const filename = sanitize(`${sanitize(request.body.name)}.json`);
118118 const pathToFile = path.join(request.user.directories.worlds, filename);
119119
120120 writeFileAtomicSync(pathToFile, JSON.stringify(request.body.data, null, 4));
src/users.js+2 -1
@@ -13,6 +13,7 @@ import mime from 'mime-types';
1313import archiver from 'archiver';
1414import _ from 'lodash';
1515import { sync as writeFileAtomicSync } from 'write-file-atomic';
16+import sanitize from 'sanitize-filename';
1617
1718import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE, UPLOADS_DIRECTORY } from './constants.js';
1819import { getConfigValue, color, delay, generateTimestamp } from './util.js';
@@ -670,7 +671,7 @@ export async function getUserAvatar(handle) {
670671 if (!avatarFile) {
671672 return PUBLIC_USER_AVATAR;
672673 }
673674 const avatarPath = path.join(directory.avatars, sanitize(avatarFile));
674675 if (!fs.existsSync(avatarPath)) {
675676 return PUBLIC_USER_AVATAR;
676677 }