fix: Enhance audiobook API validation and prevent critical meta file deletion

This commit is contained in:
Richard R 2026-01-19 16:40:04 -07:00
parent f947ece01a
commit 33d49d6966
8 changed files with 277 additions and 270 deletions

View file

@ -1,7 +1,7 @@
name: Playwright Tests
on:
push:
branches: [ main, master, 'v*.*.*' ]
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:

View file

@ -119,7 +119,7 @@ docker run --name openreader-webui \
-v /path/to/your/library:/app/docstore/library:ro \
ghcr.io/richardr1126/openreader-webui:latest
```
Seperate from the docstore volume, this will mount an external folder to `/app/docstore/library` (read-only recommended). This allows you to connect OpenReader to an existing library of documents.
Separate from the main docstore volume, this mounts an external folder into the container at `/app/docstore/library` (read-only recommended) so OpenReader can use an existing library of documents.
To import from the mounted library: **Settings → Documents → Server Library Import**

View file

@ -14,8 +14,8 @@
"@types/howler": "^2.2.12",
"@types/uuid": "^10.0.0",
"@vercel/analytics": "^1.6.1",
"cmpstr": "^3.0.4",
"compromise": "^14.14.4",
"cmpstr": "^3.1.1",
"compromise": "^14.14.5",
"core-js": "^3.47.0",
"dexie": "^4.2.1",
"dexie-react-hooks": "^4.2.0",
@ -41,10 +41,10 @@
"@eslint/eslintrc": "^3.3.3",
"@playwright/test": "^1.57.0",
"@tailwindcss/typography": "^0.5.19",
"@types/node": "^20.19.26",
"@types/react": "^19.2.7",
"@types/node": "^20.19.30",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"eslint": "^9.39.1",
"eslint": "^9.39.2",
"eslint-config-next": "^15.5.9",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.19",

File diff suppressed because it is too large Load diff

View file

@ -203,7 +203,8 @@ export async function POST(request: NextRequest) {
existingSettings?.format ??
incomingSettings?.format ??
requestedFormat;
const postSpeed = incomingSettings?.postSpeed ?? existingSettings?.postSpeed ?? 1;
const rawPostSpeed = incomingSettings?.postSpeed ?? existingSettings?.postSpeed ?? 1;
const postSpeed = Number.isFinite(Number(rawPostSpeed)) ? Number(rawPostSpeed) : 1;
// Use provided chapter index or find the next available index robustly (handles gaps)
let chapterIndex: number;

View file

@ -16,10 +16,16 @@ function getAudiobooksRootDir(request: NextRequest): string {
return safe ? join(AUDIOBOOKS_V1_DIR, safe) : AUDIOBOOKS_V1_DIR;
}
const SAFE_ID_REGEX = /^[a-zA-Z0-9._-]{1,128}$/;
function isSafeId(value: string): boolean {
return SAFE_ID_REGEX.test(value);
}
export async function GET(request: NextRequest) {
try {
const bookId = request.nextUrl.searchParams.get('bookId');
if (!bookId) {
if (!bookId || !isSafeId(bookId)) {
return NextResponse.json({ error: 'Missing bookId parameter' }, { status: 400 });
}

View file

@ -512,7 +512,7 @@ async function normalizeAudiobookDirectoryChapterLayout(intermediateDir: string)
if (/^\d+-input\.mp3$/i.test(file)) {
await unlink(path.join(intermediateDir, file)).catch(() => {});
}
if (file.endsWith('.meta.json')) {
if (file.endsWith('.meta.json') && file !== 'audiobook.meta.json') {
await unlink(path.join(intermediateDir, file)).catch(() => {});
}
}

View file

@ -75,7 +75,7 @@ async function expectChaptersBackendState(page: Page, bookId: string) {
async function resetAudiobookById(page: Page, bookId: string) {
const res = await page.request.delete(`/api/audiobook?bookId=${bookId}`);
expect(res.ok()).toBeTruthy();
expect(res.ok() || res.status() === 404).toBeTruthy();
}
async function resetAudiobookIfPresent(page: Page) {