Fix missing filename sanitization on V2 JSON character import + harden getPngName as safety nee (#5538)
* fix: sanitize character filenames on V2 JSON import and harden getPngName - Add missing sanitize() call in importFromJson V2 spec branch to match all other import paths - Sanitize data.name before readFromV2() so the name field sync happens automatically - Add sanitize() as defense-in-depth inside getPngName() to catch future oversights - Refactor getPngName() to use getUniqueName() utility for consistent name generation * fix: sanitize data.name before readFromV2 in importFromPng and importFromCharX Same bug as importFromJson: readFromV2() overwrites the top-level name with the unsanitized data.name, undoing any prior sanitize() call. Fix by sanitizing data.name before readFromV2 so the sync preserves it. * fix: sanitize top-level name field in JSON and CharX import paths * fix: incorrect path rejection in isPathUnderParent * fix: increase maxTries in getPngName --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
@@ -772,10 +772,13 @@ async function importFromCharX(uploadPath, { request }, preservedFileName) {
|
|||||||
const { card, avatar, auxiliaryAssets, extractedBuffers } = await parser.parse();
|
const { card, avatar, auxiliaryAssets, extractedBuffers } = await parser.parse();
|
||||||
|
|
||||||
// Apply standard character transformations
|
// Apply standard character transformations
|
||||||
|
if (card.data?.name) {
|
||||||
|
card.data.name = sanitize(card.data.name);
|
||||||
|
}
|
||||||
|
card.name = sanitize(card.data?.name || card.name);
|
||||||
let processedCard = readFromV2(card);
|
let processedCard = readFromV2(card);
|
||||||
unsetPrivateFields(processedCard);
|
unsetPrivateFields(processedCard);
|
||||||
processedCard.create_date = new Date().toISOString();
|
processedCard.create_date = new Date().toISOString();
|
||||||
processedCard.name = sanitize(processedCard.name);
|
|
||||||
|
|
||||||
const fileName = preservedFileName || getPngName(processedCard.name, request.user.directories);
|
const fileName = preservedFileName || getPngName(processedCard.name, request.user.directories);
|
||||||
// Use the actual character name for asset folders, not the unique filename
|
// Use the actual character name for asset folders, not the unique filename
|
||||||
@@ -887,9 +890,13 @@ async function importFromJson(uploadPath, { request }, preservedFileName) {
|
|||||||
console.info(`Importing from ${jsonData.spec} json`);
|
console.info(`Importing from ${jsonData.spec} json`);
|
||||||
importRisuSprites(request.user.directories, jsonData);
|
importRisuSprites(request.user.directories, jsonData);
|
||||||
unsetPrivateFields(jsonData);
|
unsetPrivateFields(jsonData);
|
||||||
|
if (jsonData.data?.name) {
|
||||||
|
jsonData.data.name = sanitize(jsonData.data.name);
|
||||||
|
}
|
||||||
|
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
|
||||||
jsonData = readFromV2(jsonData);
|
jsonData = readFromV2(jsonData);
|
||||||
jsonData.create_date = new Date().toISOString();
|
jsonData.create_date = new Date().toISOString();
|
||||||
const pngName = preservedFileName || getPngName(jsonData.data?.name || jsonData.name, request.user.directories);
|
const pngName = preservedFileName || getPngName(jsonData.name, request.user.directories);
|
||||||
const char = JSON.stringify(jsonData);
|
const char = JSON.stringify(jsonData);
|
||||||
const result = await writeCharacterData(DEFAULT_AVATAR_PATH, char, pngName, request);
|
const result = await writeCharacterData(DEFAULT_AVATAR_PATH, char, pngName, request);
|
||||||
return result ? pngName : '';
|
return result ? pngName : '';
|
||||||
@@ -964,6 +971,9 @@ async function importFromPng(uploadPath, { request }, preservedFileName) {
|
|||||||
|
|
||||||
let jsonData = JSON.parse(imgData);
|
let jsonData = JSON.parse(imgData);
|
||||||
|
|
||||||
|
if (jsonData.data?.name) {
|
||||||
|
jsonData.data.name = sanitize(jsonData.data.name);
|
||||||
|
}
|
||||||
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
|
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
|
||||||
const pngName = preservedFileName || getPngName(jsonData.name, request.user.directories);
|
const pngName = preservedFileName || getPngName(jsonData.name, request.user.directories);
|
||||||
|
|
||||||
@@ -1529,13 +1539,9 @@ router.post('/chats', validateAvatarUrlMiddleware, async function (request, resp
|
|||||||
* @returns {string} - The name for the uploaded PNG file
|
* @returns {string} - The name for the uploaded PNG file
|
||||||
*/
|
*/
|
||||||
function getPngName(file, directories) {
|
function getPngName(file, directories) {
|
||||||
let i = 1;
|
file = sanitize(file);
|
||||||
const baseName = file;
|
return getUniqueName(file, (name) => fs.existsSync(path.join(directories.characters, `${name}.png`)),
|
||||||
while (fs.existsSync(path.join(directories.characters, `${file}.png`))) {
|
{ nameBuilder: (base, i) => i === 0 ? base : `${base}${i}`, startIndex: 0, maxTries: 10000 }) ?? file;
|
||||||
file = baseName + i;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -1387,7 +1387,7 @@ export function isPathUnderParent(parentPath, childPath) {
|
|||||||
|
|
||||||
const relativePath = path.relative(normalizedParent, normalizedChild);
|
const relativePath = path.relative(normalizedParent, normalizedChild);
|
||||||
|
|
||||||
return !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
|
return relativePath !== '..' && !relativePath.startsWith('..' + path.sep) && !path.isAbsolute(relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user