mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-11 07:06:28 +08:00
8ebdb7452f
#### PR Dependency Tree * **PR #15181** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an additional storage backend option: asset-pack based storage (provider for avatar, blob, and copilot). * Introduced a dedicated storage runtime with provider capability reporting and expanded object operations (put/head/get/list/delete), including presigned and multipart flows where supported. * Cloudflare R2 `jurisdiction` now uses an explicit default when omitted. * **Bug Fixes** * Broadened avatar access to allow both fs and asset-pack providers. * Improved workspace blob upload completion validation and handling when stored objects are missing or mismatched. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
import { Readable } from 'node:stream';
|
|
|
|
import type { Response } from 'express';
|
|
import { getStreamAsBuffer } from 'get-stream';
|
|
|
|
import { getMime } from '../../native';
|
|
import type { BlobInputType } from './types';
|
|
|
|
export async function toBuffer(input: BlobInputType): Promise<Buffer> {
|
|
return input instanceof Readable
|
|
? await getStreamAsBuffer(input)
|
|
: input instanceof Buffer
|
|
? input
|
|
: Buffer.from(input as string);
|
|
}
|
|
|
|
const DANGEROUS_INLINE_MIME_PREFIXES = [
|
|
'text/html',
|
|
'application/xhtml+xml',
|
|
'image/svg+xml',
|
|
'application/xml',
|
|
'text/xml',
|
|
'text/javascript',
|
|
];
|
|
|
|
export function isDangerousInlineMime(mime: string | undefined) {
|
|
if (!mime) return false;
|
|
const lower = mime.toLowerCase();
|
|
return DANGEROUS_INLINE_MIME_PREFIXES.some(p => lower.startsWith(p));
|
|
}
|
|
|
|
export function applyAttachHeaders(
|
|
res: Response,
|
|
options: { filename?: string; buffer?: Buffer; contentType?: string }
|
|
) {
|
|
let { filename, buffer, contentType } = options;
|
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
if (!contentType && buffer) contentType = sniffMime(buffer);
|
|
if (contentType && isDangerousInlineMime(contentType)) {
|
|
const safeName = (filename || 'download')
|
|
.replace(/[\r\n]/g, '')
|
|
.replace(/[^\w\s.-]/g, '_');
|
|
res.setHeader(
|
|
'Content-Disposition',
|
|
`attachment; filename="${encodeURIComponent(safeName)}"; filename*=UTF-8''${encodeURIComponent(
|
|
safeName
|
|
)}`
|
|
);
|
|
}
|
|
if (!res.getHeader('Content-Type')) {
|
|
res.setHeader('Content-Type', contentType || 'application/octet-stream');
|
|
}
|
|
}
|
|
|
|
export function sniffMime(
|
|
buffer: Buffer,
|
|
declared?: string
|
|
): string | undefined {
|
|
try {
|
|
const detected = getMime(buffer);
|
|
if (detected) return detected;
|
|
} catch {}
|
|
return declared;
|
|
}
|
|
|
|
export const SIGNED_URL_EXPIRED = 60 * 60; // 1 hour
|
|
|
|
export const STORAGE_PROXY_ROOT = '/api/storage';
|
|
export const PROXY_UPLOAD_PATH = `${STORAGE_PROXY_ROOT}/upload`;
|
|
export const PROXY_MULTIPART_PATH = `${STORAGE_PROXY_ROOT}/multipart`;
|