Files
n8n-enterprise-unlocked/packages/cli/src/controllers/binary-data.controller.ts

88 lines
2.9 KiB
TypeScript

import { BinaryDataQueryDto, BinaryDataSignedQueryDto, ViewableMimeTypes } from '@n8n/api-types';
import { Get, Query, RestController } from '@n8n/decorators';
import { Request, Response } from 'express';
import { JsonWebTokenError } from 'jsonwebtoken';
import { BinaryDataService, FileNotFoundError, isValidNonDefaultMode } from 'n8n-core';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
@RestController('/binary-data')
export class BinaryDataController {
constructor(private readonly binaryDataService: BinaryDataService) {}
@Get('/')
async get(
_: Request,
res: Response,
@Query { id: binaryDataId, action, fileName, mimeType }: BinaryDataQueryDto,
) {
try {
this.validateBinaryDataId(binaryDataId);
await this.setContentHeaders(binaryDataId, action, res, fileName, mimeType);
return await this.binaryDataService.getAsStream(binaryDataId);
} catch (error) {
if (error instanceof FileNotFoundError) return res.status(404).end();
if (error instanceof BadRequestError) return res.status(400).end(error.message);
else throw error;
}
}
@Get('/signed', { skipAuth: true })
async getSigned(_: Request, res: Response, @Query { token }: BinaryDataSignedQueryDto) {
try {
const binaryDataId = this.binaryDataService.validateSignedToken(token);
this.validateBinaryDataId(binaryDataId);
await this.setContentHeaders(binaryDataId, 'download', res);
return await this.binaryDataService.getAsStream(binaryDataId);
} catch (error) {
if (error instanceof FileNotFoundError) return res.status(404).end();
if (error instanceof BadRequestError || error instanceof JsonWebTokenError)
return res.status(400).end(error.message);
else throw error;
}
}
private validateBinaryDataId(binaryDataId: string) {
if (!binaryDataId) {
throw new BadRequestError('Missing binary data ID');
}
if (!binaryDataId.includes(':')) {
throw new BadRequestError('Missing binary data mode');
}
const [mode] = binaryDataId.split(':');
if (!isValidNonDefaultMode(mode)) {
throw new BadRequestError('Invalid binary data mode');
}
}
private async setContentHeaders(
binaryDataId: string,
action: 'view' | 'download',
res: Response,
fileName?: string,
mimeType?: string,
) {
try {
const metadata = await this.binaryDataService.getMetadata(binaryDataId);
fileName = metadata.fileName ?? fileName;
mimeType = metadata.mimeType ?? mimeType;
res.setHeader('Content-Length', metadata.fileSize);
} catch {}
if (action === 'view' && (!mimeType || !ViewableMimeTypes.includes(mimeType.toLowerCase()))) {
throw new BadRequestError('Content not viewable');
}
if (mimeType) {
res.setHeader('Content-Type', mimeType);
}
if (action === 'download' && fileName) {
const encodedFilename = encodeURIComponent(fileName);
res.setHeader('Content-Disposition', `attachment; filename="${encodedFilename}"`);
}
}
}