mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 10:59:32 +01:00
fix(picker): do not offer New folder inside a cases directory
A folder made in a cases directory is already a case (no scaffold), which Link Existing then refuses by name and which deleting would take the linked folder with. The route refuses a cases directory as the parent (the shared one and the caller's own), a leading tilde in the name, and maps a vanished parent to 404. Docs now say every name it accepts is one Link Existing accepts, and the wiki describes the button.
This commit is contained in:
1 parent
869633ae6d
commit
7a3629e890
7 files changed
+68
-8
No files matched your search
@@ -57,6 +57,7 @@ import {
|
||||
findSessionOrFail,
|
||||
getAuthUser,
|
||||
parseBody,
|
||||
resolveCasesDir,
|
||||
validateSessionFilePath,
|
||||
validateSessionFilePathLexical,
|
||||
} from '../route-helpers.js';
|
||||
@@ -1468,6 +1469,21 @@ export function registerFileRoutes(app: FastifyInstance, ctx: SessionPort & Even
|
||||
reply.code(403);
|
||||
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Folders cannot be created here');
|
||||
}
|
||||
// The cases directories (shared, and the caller's own in multi-user mode) are what GET /api/cases lists as
|
||||
// local cases: a folder made there is a case with no scaffold, which Link then refuses by name and which
|
||||
// deleting would take the linked folder with. A case there is made through Add Case > New Case.
|
||||
const casesDirs = await Promise.all(
|
||||
[...new Set([CASES_DIR, resolveCasesDir(getAuthUser(req))])].map((dir) =>
|
||||
fs.realpath(dir).catch(() => resolve(dir))
|
||||
)
|
||||
);
|
||||
if (casesDirs.includes(resolvedPath)) {
|
||||
reply.code(400);
|
||||
return createErrorResponse(
|
||||
ApiErrorCode.INVALID_INPUT,
|
||||
'This is a cases folder: every folder in it is a case. Use Add Case > New Case to make one there.'
|
||||
);
|
||||
}
|
||||
// Like browse, a file is not a place to create in.
|
||||
try {
|
||||
if (!(await fs.stat(resolvedPath)).isDirectory()) throw new Error('not a directory');
|
||||
@@ -1488,6 +1504,10 @@ export function registerFileRoutes(app: FastifyInstance, ctx: SessionPort & Even
|
||||
reply.code(403);
|
||||
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'This folder is not writable');
|
||||
}
|
||||
if (code === 'ENOENT') {
|
||||
reply.code(404);
|
||||
return createErrorResponse(ApiErrorCode.NOT_FOUND, `Folder not found: ${candidatePath}`);
|
||||
}
|
||||
if (code === 'ENAMETOOLONG') {
|
||||
reply.code(400);
|
||||
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Folder name is too long');
|
||||
|
||||
+4
-4
@@ -85,8 +85,8 @@ const showHiddenQuerySchema = z.enum(['true', 'false']).optional();
|
||||
/**
|
||||
* Body for the path picker's "New folder": one folder name created directly inside `path`. The name is held
|
||||
* to the character set `safePathSchema` accepts (letters, digits, `_ - . ~` and space, no `..`), so every
|
||||
* folder this makes is one Link Existing and a session working directory will accept, and never a control
|
||||
* character. It is also one path segment and not dot-prefixed (the picker hides those by default), and at
|
||||
* name it accepts is one Link Existing and a session working directory accept (the parent's own path is
|
||||
* judged there separately), and never a control character. It is also one path segment, not dot-prefixed (the picker hides those by default) and not tilde-prefixed (a folder named `~` is an `rm -rf ~` accident), and at
|
||||
* most 255 BYTES, the usual filesystem limit.
|
||||
*/
|
||||
export const FilesystemMkdirSchema = z.object({
|
||||
@@ -99,8 +99,8 @@ export const FilesystemMkdirSchema = z.object({
|
||||
.refine((n) => /^[\p{L}\p{N}_\-. ~]+$/u.test(n), {
|
||||
message: 'Folder name can only use letters, numbers, spaces and _ - . ~',
|
||||
})
|
||||
.refine((n) => !n.includes('..') && !n.startsWith('.'), {
|
||||
message: 'Folder name cannot contain ".." or start with a dot',
|
||||
.refine((n) => !n.includes('..') && !n.startsWith('.') && !n.startsWith('~'), {
|
||||
message: 'Folder name cannot contain "..", or start with a dot or a tilde',
|
||||
}),
|
||||
sessionId: z
|
||||
.string()
|
||||
|
||||
Reference in new issue
Block a user