Request Utility
The Request utility provides functions for handling HTTP request data, such as parsing multipart form data.
parseMultipartBody
The parseMultipartBody function is an asynchronous utility for parsing multipart/form-data request bodies, which is essential for handling file uploads. It can store the uploaded files in memory or on disk.
parseMultipartBody(opts)
opts(object):context(Hono.Context): The Hono context object for the current request.storage('memory' | 'disk', optional): The storage strategy for uploaded files. Defaults to'memory'.uploadDir(string, optional): The directory to save files to when using the'disk'storage strategy. Defaults to'./uploads'.
The function returns a Promise that resolves to an array of IParsedFile objects.
IParsedFile Interface
fieldname: The name of the form field.originalname: The original name of the uploaded file.encoding: The file's encoding.mimetype: The MIME type of the file.size: The size of the file in bytes.buffer(Buffer, optional): The file's content as a Buffer (ifstorageis'memory').filename(string, optional): The name of the file on disk (ifstorageis'disk').path(string, optional): The full path to the file on disk (ifstorageis'disk').
Example
Here is an example of how to use parseMultipartBody in a controller to handle a file upload.
import { BaseController, controller, HTTP } from '@venizia/ignis';
import { parseMultipartBody } from '@venizia/ignis';
@controller({ path: '/files' })
export class FileController extends BaseController {
// ...
override binding() {
this.defineRoute({
configs: {
path: '/upload',
method: 'post',
// Note: You would typically define a request body schema
// for multipart/form-data in your OpenAPI spec.
},
handler: async (c) => {
try {
const files = await parseMultipartBody({
context: c,
storage: 'disk', // or 'memory'
uploadDir: './my-uploads',
});
console.log('Uploaded files:', files);
return c.json(
{ message: `${files.length} file(s) uploaded successfully.` },
HTTP.ResultCodes.RS_2.Ok,
);
} catch (error) {
return c.json(
{ message: 'Failed to upload files', error: error.message },
HTTP.ResultCodes.RS_5.InternalServerError,
);
}
},
});
}
}Content-Disposition Utilities
These utilities help create secure, RFC-compliant Content-Disposition headers for file downloads.
createContentDispositionHeader
Creates a safe Content-Disposition header with proper filename encoding for file downloads.
createContentDispositionHeader(filename: string): string
filename(string): The filename to use in the Content-Disposition header.
The function returns a properly formatted Content-Disposition header string with both ASCII and UTF-8 encoded filenames for maximum browser compatibility.
Features:
- Automatic filename sanitization (removes path components and dangerous characters)
- UTF-8 encoding support for international characters
- RFC 5987 compliant
- Fallback for older browsers
Example:
import { createContentDispositionHeader } from '@venizia/ignis-helpers';
// In a download endpoint
ctx.header('content-disposition', createContentDispositionHeader('my-document.pdf'));
// Output: attachment; filename="my-document.pdf"; filename*=UTF-8''my-document.pdfsanitizeFilename
Sanitizes a filename for safe use, removing path components and dangerous characters. Useful for HTTP headers (e.g., Content-Disposition) and general file handling.
sanitizeFilename(filename: string): string
filename(string): The filename to sanitize.
Returns a safe filename suitable for use in headers or filesystem operations.
Features:
- Removes path components (prevents directory traversal attacks)
- Allows only alphanumeric characters, spaces, hyphens, underscores, and dots
- Replaces dangerous characters with underscores
- Removes leading dots (prevents hidden files)
- Replaces consecutive dots with a single dot
- Removes ".." patterns (additional path traversal protection)
- Prevents empty filenames and suspicious patterns
Example:
import { sanitizeFilename } from '@venizia/ignis-helpers';
sanitizeFilename('../../etc/passwd'); // Returns: 'passwd'
sanitizeFilename('my<file>name.txt'); // Returns: 'my_file_name.txt'
sanitizeFilename('.hidden'); // Returns: 'hidden'
sanitizeFilename('file...txt'); // Returns: 'file.txt'
sanitizeFilename('документ.pdf'); // Returns: '_________.pdf'
sanitizeFilename(''); // Returns: 'download'
sanitizeFilename('..'); // Returns: 'download'encodeRFC5987
Encodes a filename according to RFC 5987 for use in HTTP headers.
encodeRFC5987(filename: string): string
filename(string): The filename to encode.
Returns an RFC 5987 encoded string suitable for the filename* parameter in Content-Disposition headers.
Example:
import { encodeRFC5987 } from '@venizia/ignis-helpers';
encodeRFC5987('my document.pdf'); // Returns: 'my%20document.pdf'
encodeRFC5987('файл.txt'); // Returns: '%D1%84%D0%B0%D0%B9%D0%BB.txt'Complete File Download Example
Here's a complete example combining multipart upload parsing with secure file downloads:
import { BaseController, controller } from '@venizia/ignis';
import { parseMultipartBody, createContentDispositionHeader, HTTP } from '@venizia/ignis-helpers';
import fs from 'node:fs';
import path from 'node:path';
@controller({ path: '/files' })
export class FileController extends BaseController {
override binding() {
// Upload endpoint
this.bindRoute({
configs: { path: '/upload', method: 'post' },
}).to({
handler: async (ctx) => {
const files = await parseMultipartBody({
context: ctx,
storage: 'disk',
uploadDir: './uploads',
});
return ctx.json(
{
message: 'Files uploaded successfully',
files: files.map(f => ({ name: f.originalname, size: f.size })),
},
HTTP.ResultCodes.RS_2.Ok,
);
},
});
// Download endpoint
this.bindRoute({
configs: { path: '/:filename', method: 'get' },
}).to({
handler: async (ctx) => {
const { filename } = ctx.req.valid('param');
const filePath = path.join('./uploads', filename);
// Read file
const fileStat = fs.statSync(filePath);
const fileStream = fs.createReadStream(filePath);
// Set secure headers
ctx.header('content-type', 'application/octet-stream');
ctx.header('content-length', fileStat.size.toString());
ctx.header('content-disposition', createContentDispositionHeader(filename));
ctx.header('x-content-type-options', 'nosniff');
return new Response(fileStream, {
headers: ctx.res.headers,
status: HTTP.ResultCodes.RS_2.Ok,
});
},
});
}
}