mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-10 22:14:10 +00:00
fix(library): validate DLC_DIR overrides
This commit is contained in:
@@ -18,6 +18,21 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an administrator-supplied DLC_DIR and accept it only when it
|
||||
* already names a directory. Invalid overrides must not shadow config.json.
|
||||
*/
|
||||
export function normalizeExplicitLibraryPath(rawPath?: string): string | undefined {
|
||||
const candidate = (rawPath || '').trim();
|
||||
if (!candidate) return undefined;
|
||||
|
||||
try {
|
||||
return fs.statSync(candidate).isDirectory() ? candidate : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the library path contract for the Python backend.
|
||||
*
|
||||
@@ -31,7 +46,7 @@ export function prepareLibraryPathForPython(
|
||||
resolvedDlcDir: string,
|
||||
explicitDlcDir?: string,
|
||||
): LibraryPathPreparation {
|
||||
const override = (explicitDlcDir || '').trim();
|
||||
const override = normalizeExplicitLibraryPath(explicitDlcDir);
|
||||
if (override) {
|
||||
return {
|
||||
status: 'explicit-override',
|
||||
|
||||
+5
-7
@@ -11,7 +11,7 @@ import * as net from 'net';
|
||||
import * as os from 'os';
|
||||
import { getActiveSoundfontPath, getDesktopConfig } from './soundfont-manager';
|
||||
import { isDebugEnabled } from './debug-log';
|
||||
import { prepareLibraryPathForPython } from './library-path-config';
|
||||
import { normalizeExplicitLibraryPath, prepareLibraryPathForPython } from './library-path-config';
|
||||
|
||||
let pythonProcess: ChildProcess | null = null;
|
||||
// A backend that is being *gracefully* stopped (SIGTERM sent, async SIGKILL
|
||||
@@ -424,8 +424,8 @@ function getPluginsDir(): string {
|
||||
return pluginsDir;
|
||||
}
|
||||
|
||||
function getDLCDir(): string {
|
||||
if (process.env.DLC_DIR && fs.existsSync(process.env.DLC_DIR)) return process.env.DLC_DIR;
|
||||
function getDLCDir(explicitDlcDir = normalizeExplicitLibraryPath(process.env.DLC_DIR)): string {
|
||||
if (explicitDlcDir) return explicitDlcDir;
|
||||
|
||||
// Read from shared config
|
||||
const configFile = path.join(getConfigDir(), 'config.json');
|
||||
@@ -497,7 +497,8 @@ export async function startPython(): Promise<void> {
|
||||
}
|
||||
serverPort = await findPort(PREFERRED_PORT);
|
||||
const configDir = getConfigDir();
|
||||
const dlcDir = getDLCDir();
|
||||
const explicitDlcDir = normalizeExplicitLibraryPath(process.env.DLC_DIR);
|
||||
const dlcDir = getDLCDir(explicitDlcDir);
|
||||
// Ensure the resolved library folder exists before the server starts. The
|
||||
// Python side only seeds starter content (and scans) when DLC_DIR.is_dir()
|
||||
// is true, and it can't bootstrap the folder itself (the seed's mkdir runs
|
||||
@@ -514,9 +515,6 @@ export async function startPython(): Promise<void> {
|
||||
// instead. The backend re-reads that file for every scan, so a path saved in
|
||||
// Settings takes effect immediately rather than being shadowed by the
|
||||
// startup path until the Python process restarts.
|
||||
const explicitDlcDir = process.env.DLC_DIR && fs.existsSync(process.env.DLC_DIR)
|
||||
? process.env.DLC_DIR
|
||||
: undefined;
|
||||
const libraryPath = prepareLibraryPathForPython(configDir, dlcDir, explicitDlcDir);
|
||||
if (libraryPath.error) {
|
||||
console.warn(`[python] could not prepare dynamic library path (${libraryPath.status}): ${libraryPath.error}`);
|
||||
|
||||
@@ -8,6 +8,7 @@ const path = require('node:path');
|
||||
const { loadTs, ROOT } = require('./_load-ts');
|
||||
|
||||
const {
|
||||
normalizeExplicitLibraryPath,
|
||||
prepareLibraryPathForPython,
|
||||
} = loadTs('src/main/library-path-config.ts');
|
||||
|
||||
@@ -58,19 +59,39 @@ test('an existing saved library stays config-owned and can change between scans'
|
||||
|
||||
test('an explicit valid DLC_DIR remains an environment override', () => {
|
||||
const configDir = tmpConfigDir();
|
||||
const managedSongs = path.join(configDir, 'Managed Songs');
|
||||
fs.mkdirSync(managedSongs);
|
||||
const result = prepareLibraryPathForPython(
|
||||
configDir,
|
||||
'C:\\Default Songs',
|
||||
' D:\\Managed Songs ',
|
||||
` ${managedSongs} `,
|
||||
);
|
||||
|
||||
assert.deepEqual(result, {
|
||||
status: 'explicit-override',
|
||||
environmentDlcDir: 'D:\\Managed Songs',
|
||||
environmentDlcDir: managedSongs,
|
||||
});
|
||||
assert.equal(fs.existsSync(path.join(configDir, 'config.json')), false);
|
||||
});
|
||||
|
||||
test('an explicit DLC_DIR rejects whitespace, files, and missing paths', () => {
|
||||
const root = tmpConfigDir();
|
||||
const file = path.join(root, 'not-a-directory');
|
||||
fs.writeFileSync(file, 'x');
|
||||
|
||||
assert.equal(normalizeExplicitLibraryPath(' '), undefined);
|
||||
assert.equal(normalizeExplicitLibraryPath(file), undefined);
|
||||
assert.equal(normalizeExplicitLibraryPath(path.join(root, 'missing')), undefined);
|
||||
});
|
||||
|
||||
test('an explicit DLC_DIR is trimmed before directory validation', () => {
|
||||
const root = tmpConfigDir();
|
||||
const directory = path.join(root, 'Managed Songs');
|
||||
fs.mkdirSync(directory);
|
||||
|
||||
assert.equal(normalizeExplicitLibraryPath(` ${directory} `), directory);
|
||||
});
|
||||
|
||||
test('a corrupt config is never overwritten during bootstrap', () => {
|
||||
const configDir = tmpConfigDir();
|
||||
const configFile = path.join(configDir, 'config.json');
|
||||
@@ -86,6 +107,8 @@ test('a corrupt config is never overwritten during bootstrap', () => {
|
||||
test('python startup does not pin its resolved fallback as DLC_DIR', () => {
|
||||
const source = fs.readFileSync(path.join(ROOT, 'src', 'main', 'python.ts'), 'utf8');
|
||||
|
||||
assert.match(source, /normalizeExplicitLibraryPath\(process\.env\.DLC_DIR\)/);
|
||||
assert.doesNotMatch(source, /existsSync\(process\.env\.DLC_DIR\)/);
|
||||
assert.match(source, /prepareLibraryPathForPython\(configDir, dlcDir, explicitDlcDir\)/);
|
||||
assert.doesNotMatch(source, /DLC_DIR:\s*dlcDir/);
|
||||
assert.match(source, /delete pythonEnv\.DLC_DIR/);
|
||||
|
||||
Reference in New Issue
Block a user