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