Files
AFFiNE-Mirror/packages/backend/server/src/base/utils/stream.ts
T
DarkSky 1b859a37c5 feat: improve attachment headers (#13709)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Safer, consistent file downloads with automatic attachment headers and
filenames.
- Smarter MIME detection for uploads (avatars, workspace blobs, Copilot
files/transcripts).
  - Sensible default buffer limit when reading uploads.

- **Bug Fixes**
- Prevents risky content from rendering inline by forcing downloads and
adding no‑sniff protection.
- More accurate content types when original metadata is missing or
incorrect.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-09 08:04:18 +00:00

72 lines
1.8 KiB
TypeScript

import { Readable } from 'node:stream';
import { BlobQuotaExceeded, StorageQuotaExceeded } from '../error';
import { OneKB } from './unit';
export type CheckExceededResult =
| {
storageQuotaExceeded: boolean;
blobQuotaExceeded: boolean;
}
| undefined;
export async function readBuffer(
readable: Readable,
checkExceeded: (recvSize: number) => CheckExceededResult
): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
const chunks: Uint8Array[] = [];
let totalSize = 0;
let result: CheckExceededResult;
readable.on('data', chunk => {
totalSize += chunk.length;
// check size after receive each chunk to avoid unnecessary memory usage
result = checkExceeded(totalSize);
if (result?.blobQuotaExceeded) {
reject(new BlobQuotaExceeded());
} else if (result?.storageQuotaExceeded) {
reject(new StorageQuotaExceeded());
}
if (checkExceeded(totalSize)) {
reject(new BlobQuotaExceeded());
readable.destroy(new BlobQuotaExceeded());
return;
}
chunks.push(chunk);
});
readable.on('error', reject);
readable.on('end', () => {
const buffer = Buffer.concat(chunks, totalSize);
if (checkExceeded(buffer.length)) {
reject(new BlobQuotaExceeded());
} else {
resolve(buffer);
}
});
});
}
export async function readBufferWithLimit(
readable: Readable,
limit: number = 500 * OneKB
): Promise<Buffer> {
return readBuffer(readable, size =>
size > limit
? { blobQuotaExceeded: true, storageQuotaExceeded: false }
: undefined
);
}
export async function readableToBuffer(readable: Readable) {
const chunks: Buffer[] = [];
for await (const chunk of readable) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}