Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
78 lines
2.1 KiB
TypeScript
78 lines
2.1 KiB
TypeScript
const MEBIBYTE = 1024 * 1024
|
|
|
|
export const MAX_LOGICAL_UPLOAD_BYTES = 10 * 1024 * MEBIBYTE
|
|
export const MAX_UPLOAD_CHUNK_BYTES = 20 * MEBIBYTE
|
|
export const MAX_UPLOAD_CHUNKS = Math.ceil(MAX_LOGICAL_UPLOAD_BYTES / (10 * MEBIBYTE))
|
|
|
|
const LEGACY_BROWSER_CHUNK_SIZES = [10 * MEBIBYTE, 20 * MEBIBYTE]
|
|
|
|
/**
|
|
* Resolve and validate the physical chunk size used for one upload session.
|
|
* Older already-open browser pages did not send chunkSize to init; only the two
|
|
* historically supported 10/20 MiB layouts are inferred for those sessions.
|
|
*/
|
|
export function resolveUploadChunkSize(
|
|
fileSize: number,
|
|
totalChunks: number,
|
|
rawChunkSize?: unknown,
|
|
): number | null {
|
|
if (
|
|
!Number.isSafeInteger(fileSize)
|
|
|| fileSize <= 0
|
|
|| fileSize > MAX_LOGICAL_UPLOAD_BYTES
|
|
|| !Number.isInteger(totalChunks)
|
|
|| totalChunks <= 0
|
|
|| totalChunks > MAX_UPLOAD_CHUNKS
|
|
) {
|
|
return null
|
|
}
|
|
|
|
const hasExplicitChunkSize = rawChunkSize !== undefined
|
|
&& rawChunkSize !== null
|
|
&& String(rawChunkSize).trim() !== ''
|
|
|
|
if (hasExplicitChunkSize) {
|
|
const chunkSize = Number(rawChunkSize)
|
|
if (
|
|
!Number.isSafeInteger(chunkSize)
|
|
|| chunkSize <= 0
|
|
|| chunkSize > MAX_UPLOAD_CHUNK_BYTES
|
|
|| Math.ceil(fileSize / chunkSize) !== totalChunks
|
|
) {
|
|
return null
|
|
}
|
|
return chunkSize
|
|
}
|
|
|
|
return LEGACY_BROWSER_CHUNK_SIZES.find(
|
|
(chunkSize) => Math.ceil(fileSize / chunkSize) === totalChunks,
|
|
) || null
|
|
}
|
|
|
|
export function getExpectedUploadChunkBytes(input: {
|
|
fileSize: number
|
|
totalChunks: number
|
|
chunkSize: number
|
|
chunkIndex: number
|
|
}): number | null {
|
|
const { fileSize, totalChunks, chunkSize, chunkIndex } = input
|
|
if (
|
|
!Number.isInteger(chunkIndex)
|
|
|| chunkIndex < 0
|
|
|| chunkIndex >= totalChunks
|
|
|| resolveUploadChunkSize(fileSize, totalChunks, chunkSize) !== chunkSize
|
|
) {
|
|
return null
|
|
}
|
|
|
|
const expectedBytes = chunkIndex === totalChunks - 1
|
|
? fileSize - chunkSize * (totalChunks - 1)
|
|
: chunkSize
|
|
|
|
return Number.isSafeInteger(expectedBytes)
|
|
&& expectedBytes > 0
|
|
&& expectedBytes <= MAX_UPLOAD_CHUNK_BYTES
|
|
? expectedBytes
|
|
: null
|
|
}
|