From acf2b165f3f8bf225a23baaee3e0e454849ec8d3 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 22 Jun 2026 21:53:59 +0800 Subject: [PATCH] fix(library): keep in-place book paths absolute so uploads stay in fs scope (#4720) (#4730) * fix(library): keep in-place book paths absolute so uploads stay in fs scope (#4720) resolveFilePath joined `${prefix}/${path}` unconditionally. For base 'None' (in-place / external books, whose filePath lives outside Books//) the prefix is empty, so an already-absolute source path became `/C:\Users\...` on Windows (and `//Users/...` on POSIX). The native upload guard added in #4639 (transfer_file.rs `ensure_path_allowed`) then rejected that malformed path as "permission denied: path not in filesystem scope", so uploading a folder-imported book failed on Windows. Return the path verbatim when the prefix is empty. Co-Authored-By: Claude Opus 4.8 (1M context) * style(library): use btn-contrast for the Import-from-folder confirm button (#4720) Aligns the dialog's confirm CTA with the sibling import dialogs (ImportFromUrlDialog, FailedImportsDialog), which already use the theme-neutral, e-ink-correct btn-contrast instead of the colored btn-primary. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .../src/__tests__/services/app-service.test.ts | 16 ++++++++++++++++ .../components/ImportFromFolderDialog.tsx | 2 +- apps/readest-app/src/services/appService.ts | 8 +++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/apps/readest-app/src/__tests__/services/app-service.test.ts b/apps/readest-app/src/__tests__/services/app-service.test.ts index ae8c799d..3ee139f9 100644 --- a/apps/readest-app/src/__tests__/services/app-service.test.ts +++ b/apps/readest-app/src/__tests__/services/app-service.test.ts @@ -269,6 +269,22 @@ describe('BaseAppService', () => { const result = await service.resolveFilePath('', 'Data'); expect(result).toBe('/base/books'); }); + + // base 'None' carries a complete, absolute source path (in-place / + // external books point `book.filePath` outside Books//). Its prefix + // is empty, so the path must be returned verbatim. Prepending a separator + // produced `/C:\Users\...` on Windows (and `//Users/...` on POSIX), which + // the native upload guard (transfer_file.rs `ensure_path_allowed`) rejects + // as "path not in filesystem scope" — issue #4720. + test('returns an absolute path unchanged when the prefix is empty (base None)', async () => { + vi.mocked(mockFs.getPrefix).mockResolvedValue(''); + + const windowsPath = "C:\\Users\\me\\Documents\\books\\Alice's Adventures.epub"; + expect(await service.resolveFilePath(windowsPath, 'None')).toBe(windowsPath); + + const posixPath = '/Users/me/Documents/books/Alice.epub'; + expect(await service.resolveFilePath(posixPath, 'None')).toBe(posixPath); + }); }); describe('settings', () => { diff --git a/apps/readest-app/src/app/library/components/ImportFromFolderDialog.tsx b/apps/readest-app/src/app/library/components/ImportFromFolderDialog.tsx index 4a8c79fb..e1f831c3 100644 --- a/apps/readest-app/src/app/library/components/ImportFromFolderDialog.tsx +++ b/apps/readest-app/src/app/library/components/ImportFromFolderDialog.tsx @@ -434,7 +434,7 @@ const ImportFromFolderDialog: React.FC = ({