136 lines · 4.9 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:off - FileSystem does not expose no-follow// or non-blocking open flags, and the response must keep the validated descriptor.import * as NodeFS from "node:fs";import * as NodeFSP from "node:fs/promises"; import * as NodeStream from "@effect/platform-node/NodeStream";import * as Effect from "effect/Effect";import * as Schema from "effect/Schema"; class MediaFileOpenError extends Schema.TaggedError<MediaFileOpenError>()("MediaFileOpenError", {  path: Schema.String,  cause: Schema.Defect(),}) {  override get message(): string {    return `Failed to open media file '${this.path}'.`;  }} class MediaFileReadError extends Schema.TaggedError<MediaFileReadError>()("MediaFileReadError", {  path: Schema.String,  cause: Schema.Defect(),}) {  override get message(): string {    return `Failed to read media file '${this.path}'.`;  }} class MediaFileStatError extends Schema.TaggedError<MediaFileStatError>()("MediaFileStatError", {  path: Schema.String,  cause: Schema.Defect(),}) {  override get message(): string {    return `Failed to read metadata for media file '${this.path}'.`;  }} /** Holds the file identity and descriptor for one HTTP request, never a copy of its bytes. */export interface OpenMediaFile {  readonly handle: NodeFSP.FileHandle;  readonly info: NodeFS.BigIntStats;} const realpathLikeFileSystem = (filePath: string) =>  new Promise<string>((resolve, reject) => {    NodeFS.realpath(filePath, (error, resolved) => (error ? reject(error) : resolve(resolved)));  }); /** Opens a canonical media path once. Replacements cannot change the response's source. */export const openMediaFile = Effect.fn("openMediaFile")(function* (  filePath: string,  identity?: { readonly device: string; readonly inode: string },) {  return yield* Effect.acquireRelease(    Effect.tryPromise({      try: async () => {        const before = await NodeFSP.lstat(filePath, { bigint: true });        if (!before.isFile() || before.ino === 0n) return null;        if (          identity &&          (before.dev.toString() !== identity.device || before.ino.toString() !== identity.inode)        ) {          return null;        }         // Windows lacks these flags; the descriptor/path identity checks still apply.        const handle = await NodeFSP.open(          filePath,          NodeFS.constants.O_RDONLY |            (NodeFS.constants.O_NOFOLLOW ?? 0) |            (NodeFS.constants.O_NONBLOCK ?? 0),        );        let accepted = false;        try {          const info = await handle.stat({ bigint: true });          if (!info.isFile() || info.dev !== before.dev || info.ino !== before.ino) return null;          if (            identity &&            (info.dev.toString() !== identity.device || info.ino.toString() !== identity.inode)          ) {            return null;          }          // Callers canonicalise with Effect's FileSystem.realPath, which is          // Node's JS realpath. fs/promises.realpath is the native binding and          // on Windows also expands 8.3 short names, so a path that is already          // canonical by the caller's rules would still look swapped here.          if ((await realpathLikeFileSystem(filePath)) !== filePath) return null;          const after = await NodeFSP.lstat(filePath, { bigint: true });          if (!after.isFile() || info.dev !== after.dev || info.ino !== after.ino) return null;          accepted = true;          return { handle, info } satisfies OpenMediaFile;        } finally {          if (!accepted) await handle.close();        }      },      catch: (cause) => new MediaFileOpenError({ path: filePath, cause }),    }),    (file) => (file ? Effect.promise(() => file.handle.close()) : Effect.void),  );}); /** Reads the leading bytes of an already-validated media file, never past the end. */export const readMediaFileHeader = (filePath: string, file: OpenMediaFile, byteCount: number) =>  Effect.tryPromise({    try: async () => {      const buffer = new Uint8Array(byteCount);      const { bytesRead } = await file.handle.read(buffer, 0, byteCount, 0);      return buffer.subarray(0, bytesRead);    },    catch: (cause) => new MediaFileReadError({ path: filePath, cause }),  }); export const statMediaFile = Effect.fn("statMediaFile")(function* (  filePath: string,  file: OpenMediaFile,) {  return yield* Effect.tryPromise({    try: () => file.handle.stat({ bigint: true }),    catch: (cause) => new MediaFileStatError({ path: filePath, cause }),  });}); export const streamMediaFile = (file: OpenMediaFile, offset: bigint, bytesToRead: bigint) => {  const start = Number(offset);  const end = Number(offset + bytesToRead - 1n);  if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start) {    return null;  }  return NodeStream.fromReadable<Uint8Array>({    evaluate: () =>      file.handle.createReadStream({        autoClose: false,        start,        end,      }),  });};