implement safe path handling
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
This commit is contained in:
parent
63c65e8b7d
commit
6b65fda69d
@ -1,5 +1,7 @@
|
||||
import path from 'path';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import formatDuration from '$lib/formatDuration';
|
||||
import { safePath } from '$lib/server/safePath';
|
||||
|
||||
describe('formatDuration test', () => {
|
||||
it('converts 120 seconds to "2:00"', () => {
|
||||
@ -21,4 +23,26 @@ describe('formatDuration test', () => {
|
||||
it('converts 3601 seconds to "1:00:01"', () => {
|
||||
expect(formatDuration(3601)).toBe('1:00:01');
|
||||
});
|
||||
});
|
||||
|
||||
describe('safePath test', () => {
|
||||
const base = "data/subdir";
|
||||
it('rejects empty string', () => {
|
||||
expect(safePath('', { base })).toBe(null);
|
||||
});
|
||||
it('accepts a regular path', () => {
|
||||
expect(safePath('subsubdir/file.txt', { base })).toBe('data/subdir/subsubdir/file.txt');
|
||||
});
|
||||
it('rejects path with ..', () => {
|
||||
expect(safePath('../file.txt', { base })).toBe(null);
|
||||
});
|
||||
it('accepts path with .', () => {
|
||||
expect(safePath('./file.txt', { base })).toBe('data/subdir/file.txt');
|
||||
});
|
||||
it('accepts path traversal within base', () => {
|
||||
expect(safePath('subsubdir/../file.txt', { base })).toBe('data/subdir/file.txt');
|
||||
});
|
||||
it('rejects path with subdir if noSubDir is true', () => {
|
||||
expect(safePath('subsubdir/file.txt', { base, noSubDir: true })).toBe(null);
|
||||
});
|
||||
});
|
@ -2,6 +2,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { globalMemoryStorage, songData, songNameCache } from '$lib/server/cache.js';
|
||||
import { getDirectoryHash } from '../dirHash';
|
||||
import { safePath } from '../safePath';
|
||||
|
||||
const dataPath = './data/song/';
|
||||
|
||||
@ -26,7 +27,12 @@ export async function loadData() {
|
||||
songNameCache.flushAll();
|
||||
for (const songID of songList) {
|
||||
try {
|
||||
const fileContentString = fs.readFileSync(path.join(dataPath, songID + '.json')).toString();
|
||||
const normPath = safePath(songID + '.json', { base: dataPath });
|
||||
if (!normPath) {
|
||||
console.error(`[load-song-data] Invalid path for song ID ${songID}`);
|
||||
continue;
|
||||
}
|
||||
const fileContentString = fs.readFileSync(normPath).toString();
|
||||
const data = JSON.parse(fileContentString);
|
||||
songData.set(songID, data);
|
||||
const metadata: MusicMetadata = data;
|
||||
|
24
src/lib/server/safePath.ts
Normal file
24
src/lib/server/safePath.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import path from 'path';
|
||||
|
||||
export function safePath(pathIn: string, options: { base: string, noSubDir?: boolean }): string | null {
|
||||
const base = options.base.endsWith('/') ? options.base : options.base + '/';
|
||||
if (!pathIn.startsWith("./")) {
|
||||
pathIn = "./" + pathIn;
|
||||
}
|
||||
pathIn = path.join(base, pathIn);
|
||||
const normBase = path.normalize(base);
|
||||
const normPath = path.normalize(pathIn);
|
||||
console.log(normBase);
|
||||
console.log(normPath);
|
||||
|
||||
if (normPath !== normBase && normPath.startsWith(normBase)) {
|
||||
if (options.noSubDir) {
|
||||
let rel = path.relative(normBase, normPath);
|
||||
if (rel.indexOf(path.sep) !== -1) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return normPath;
|
||||
}
|
||||
return null;
|
||||
}
|
@ -1,10 +1,16 @@
|
||||
import { safePath } from '$lib/server/safePath';
|
||||
import { getCurrentFormattedDateTime } from '$lib/songUpdateTime';
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export async function GET({ params }) {
|
||||
const filePath = path.join('./data/song', `${params.id}.json`);
|
||||
const filePath = safePath(`${params.id}.json`, { base: './data/song' });
|
||||
if (!filePath) {
|
||||
return error(404, {
|
||||
message: "No correspoding song."
|
||||
});
|
||||
}
|
||||
let data;
|
||||
try { data = fs.readFileSync(filePath); } catch (e) {
|
||||
return error(404, {
|
||||
|
@ -1,10 +1,16 @@
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
import { safePath } from '$lib/server/safePath';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
|
||||
export function load({ params }) {
|
||||
const filePath = path.join('./data/song', `${params.id}.json`);
|
||||
const filePath = safePath(`${params.id}.json`, { base: './data/song' });
|
||||
if (!filePath) {
|
||||
return {
|
||||
songData: null
|
||||
};
|
||||
}
|
||||
try {
|
||||
const dataBuffer = fs.readFileSync(filePath);
|
||||
const data = JSON.parse(dataBuffer.toString());
|
||||
|
Loading…
Reference in New Issue
Block a user