T-HUB/apps/api/src/group-files/playback-range.ts
glass beadian b24ebc5862 fix: logic-audit P0/P1 wave — CORS PUT, chunked cap, presign checksums, logout await, refresh rotation race
From the logic audit (HEAD 04f5f7e):
- P0 CORS: enableCors methods lacked PUT — browsers could not chunk-upload
  at all (cross-origin preflight refused)
- P0 chunked: validateUpload enforced the 2 GiB whole-body cap on chunked
  completes, so every >2 GiB chunked upload failed after full upload
- P1 presignPut: the SDK's flexible-checksum default added an
  x-amz-checksum-crc32 header browsers cannot reproduce; S3Client now uses
  requestChecksumCalculation WHEN_REQUIRED
- P1 refresh rotation race: concurrent refreshes both succeeded (consume
  ignored rowCount) forking the token family; consume now returns whether
  it won and the loser revokes the chain
- P2 logout: awaits the revocation and ignores missing/forged bearers
honestly
2026-10-18 10:18:02 +00:00

37 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { HttpException, HttpStatus } from "@nestjs/common";
/** Carries the plaintext size for the controller’s unsatisfied Content-Range header. */
export class FileRangeNotSatisfiableException extends HttpException {
constructor(readonly total: number) {
super("requested range is not satisfiable", HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
}
}
/** Inclusive HTTP byte bounds, clipped to the plaintext EOF. */
export function playbackRange(total: number, start: number, end: number): { start: number; end: number } {
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || start >= total || end < start) {
throw new FileRangeNotSatisfiableException(total);
}
return { start, end: Math.min(end, total - 1) };
}
/** Query bounds take precedence; also accept the single Range browsers send. */
export function requestedPlaybackRange(total: number, start: unknown, end: unknown, range?: string): { start: number; end: number } {
const fail = (): never => { throw new FileRangeNotSatisfiableException(total); };
const integer = (value: unknown): number => {
if (typeof value !== "string" || !/^\d+$/.test(value) || !Number.isSafeInteger(Number(value))) return fail();
return Number(value);
};
if (start !== undefined || end !== undefined) {
return playbackRange(total, start === undefined ? 0 : integer(start), end === undefined ? total - 1 : integer(end));
}
if (range === undefined) return playbackRange(total, 0, total - 1);
const match = /^bytes=(\d*)-(\d*)$/.exec(range);
if (match === null || (match[1] === "" && match[2] === "")) return fail();
if (match[1] === "") {
const suffix = integer(match[2]);
if (suffix === 0) return fail();
return playbackRange(total, Math.max(0, total - suffix), total - 1);
}
return playbackRange(total, integer(match[1]), match[2] === "" ? total - 1 : integer(match[2]));
}