Restore the community photo after reload and reject empty, oversized, SVG, and spoofed uploads.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,12 +2,12 @@ import { writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { CreateFlowUploadPurpose } from "./uploadConstants";
|
||||
import {
|
||||
extensionForMime,
|
||||
isAllowedMime,
|
||||
maxBytesForPurpose,
|
||||
} from "./uploadConstants";
|
||||
import { extensionForMime } from "./uploadConstants";
|
||||
import { ensureUploadRootExists, getUploadRootFromEnv } from "./uploadRoot";
|
||||
import {
|
||||
validateCreateFlowUploadBytes,
|
||||
type CreateFlowUploadValidationReason,
|
||||
} from "../../create/createFlowUploadValidation";
|
||||
|
||||
export type SaveCreateFlowUploadResult = {
|
||||
/** Filename stem (UUID) without extension — used in GET URL. */
|
||||
@@ -18,30 +18,32 @@ export type SaveCreateFlowUploadResult = {
|
||||
byteLength: number;
|
||||
};
|
||||
|
||||
export type SaveCreateFlowUploadFailure = {
|
||||
error: "misconfigured" | "validation";
|
||||
reason?: CreateFlowUploadValidationReason;
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes bytes under `UPLOAD_ROOT/{id}{ext}` and returns a stable app URL path.
|
||||
* Trusts sniffed bytes, not the client-declared MIME type.
|
||||
*/
|
||||
export async function saveCreateFlowUpload(params: {
|
||||
purpose: CreateFlowUploadPurpose;
|
||||
buffer: Buffer;
|
||||
/** Declared MIME from the client `File.type` (validated server-side). */
|
||||
mimeType: string;
|
||||
}): Promise<SaveCreateFlowUploadResult | { error: "misconfigured" | "validation" }> {
|
||||
}): Promise<SaveCreateFlowUploadResult | SaveCreateFlowUploadFailure> {
|
||||
const root = getUploadRootFromEnv();
|
||||
if (!root) {
|
||||
return { error: "misconfigured" };
|
||||
}
|
||||
|
||||
const { purpose, buffer, mimeType } = params;
|
||||
if (buffer.length > maxBytesForPurpose(purpose)) {
|
||||
return { error: "validation" };
|
||||
}
|
||||
if (!isAllowedMime(purpose, mimeType)) {
|
||||
return { error: "validation" };
|
||||
const { purpose, buffer } = params;
|
||||
const validated = validateCreateFlowUploadBytes(purpose, buffer);
|
||||
if (validated.ok === false) {
|
||||
return { error: "validation", reason: validated.reason };
|
||||
}
|
||||
|
||||
const id = randomUUID();
|
||||
const ext = extensionForMime(mimeType);
|
||||
const ext = extensionForMime(validated.mimeType);
|
||||
const fileName = `${id}${ext}`;
|
||||
const absolutePath = path.join(root, fileName);
|
||||
|
||||
@@ -51,7 +53,7 @@ export async function saveCreateFlowUpload(params: {
|
||||
return {
|
||||
id,
|
||||
urlPath: `/api/uploads/${id}`,
|
||||
mimeType: mimeType.toLowerCase().split(";")[0]?.trim() ?? "application/octet-stream",
|
||||
mimeType: validated.mimeType,
|
||||
byteLength: buffer.length,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user