MIME-тип (Multipurpose Internet Mail Extensions) — строковый идентификатор формата данных, который используется для описания содержимого файла или HTTP-сообщения. MIME-тип позволяет определить, как браузер, сервер или приложение должны интерпретировать данные.
Примеры MIME-типов:
| MIME-тип | Описание |
|---|---|
text/plain |
Обычный текст |
text/html |
HTML-документ |
application/json |
JSON-данные |
image/png |
PNG-изображение |
image/jpeg |
JPEG-изображение |
audio/mpeg |
MP3-аудио |
video/mp4 |
MP4-видео |
Валидация MIME-типов применяется:
Validator.js предоставляет функцию isMimeType(),
предназначенную для проверки корректности MIME-строки.
npm install validator
Подключение библиотеки:
const validator = require('validator');
Для ES Modules:
import validator from 'validator';
isMimeType()validator.isMimeType(str)
| Параметр | Тип | Описание |
|---|---|---|
str |
string | Проверяемая MIME-строка |
| Тип | Описание |
|---|---|
| boolean | true, если MIME-тип корректен |
validator.isMimeType('text/plain');
true
validator.isMimeType('application/json');
true
validator.isMimeType('image/png');
true
validator.isMimeType('plain/text');
false
validator.isMimeType('image');
false
validator.isMimeType('application/');
false
validator.isMimeType('/json');
false
MIME-тип состоит из двух частей:
type/subtype
Пример:
image/jpeg
Где:
| Часть | Значение |
|---|---|
image |
основной тип |
jpeg |
подтип |
text/plain
text/html
text/css
text/csv
image/png
image/jpeg
image/gif
image/webp
video/mp4
video/webm
video/ogg
audio/mpeg
audio/wav
audio/ogg
application/json
application/pdf
application/zip
application/xml
npm install express multer validator
const express = require('express');
const multer = require('multer');
const validator = require('validator');
const app = express();
const upload = multer({
storage: multer.memoryStorage(),
fileFilter(req, file, cb) {
if (!validator.isMimeType(file.mimetype)) {
return cb(new Error('Некорректный MIME-тип'));
}
cb(null, true);
}
});
app.post('/upload', upload.single('file'), (req, res) => {
res.send('Файл загружен');
});
Проверка только корректности MIME-типа недостаточна. Необходимо ограничивать список разрешённых значений.
const allowedMimeTypes = [
'image/png',
'image/jpeg',
'image/webp'
];
function isAllowedMimeType(mime) {
return (
validator.isMimeType(mime) &&
allowedMimeTypes.includes(mime)
);
}
function isPdf(mime) {
return mime === 'application/pdf';
}
function isImage(mime) {
if (!validator.isMimeType(mime)) {
return false;
}
return mime.startsWith('image/');
}
function isAudio(mime) {
if (!validator.isMimeType(mime)) {
return false;
}
return mime.startsWith('audio/');
}
function isVideo(mime) {
if (!validator.isMimeType(mime)) {
return false;
}
return mime.startsWith('video/');
}
Иногда MIME-типы проверяются через RegExp:
const imageMimeRegex = /^image\/.+$/;
Использование:
imageMimeRegex.test('image/png');
true
Однако Validator.js обеспечивает дополнительную базовую проверку структуры MIME-типа.
function validateImageMime(mime) {
if (!validator.isMimeType(mime)) {
return false;
}
return /^image\/(png|jpeg|webp|gif)$/.test(mime);
}
HTTP-заголовок:
Content-Type: application/json
Проверка:
const contentType = req.headers['content-type'];
if (!validator.isMimeType(contentType)) {
throw new Error('Некорректный Content-Type');
}
Многие заголовки содержат параметры:
Content-Type: text/html; charset=utf-8
validator.isMimeType() проверяет только MIME-тип,
поэтому строка с параметрами вернёт false.
validator.isMimeType('text/html; charset=utf-8');
false
function extractMimeType(contentType) {
if (!contentType) {
return '';
}
return contentType.split(';')[0].trim();
}
Использование:
const mime = extractMimeType(
'text/html; charset=utf-8'
);
validator.isMimeType(mime);
true
app.use((req, res, next) => {
const contentType = req.headers['content-type'];
if (!contentType) {
return res.status(400).send('Content-Type обязателен');
}
const mime = contentType.split(';')[0];
if (!validator.isMimeType(mime)) {
return res.status(400).send('Некорректный Content-Type');
}
next();
});
import {
PipeTransform,
Injectable,
BadRequestException
} from '@nestjs/common';
import validator from 'validator';
@Injectable()
export class MimeTypePipe
implements PipeTransform {
transform(value) {
if (!validator.isMimeType(value)) {
throw new BadRequestException(
'Некорректный MIME-тип'
);
}
return value;
}
}
fastify.addHook('preHandler', async (request) => {
const contentType =
request.headers['content-type'];
if (!contentType) {
throw new Error('Content-Type отсутствует');
}
const mime = contentType.split(';')[0];
if (!validator.isMimeType(mime)) {
throw new Error('Некорректный MIME');
}
});
Проверка MIME-типов играет важную роль в безопасности приложений.
| Угроза | Описание |
|---|---|
| Подмена расширения | virus.exe → image.jpg |
| Загрузка исполняемых файлов | .exe, .bat, .sh |
| XSS через HTML | загрузка HTML вместо изображения |
| SVG-инъекции | вредоносный JavaScript внутри SVG |
| Маскировка контента | поддельный Content-Type |
MIME-тип часто приходит:
Следовательно, MIME может быть подделан.
Для критически важных систем MIME следует проверять совместно с сигнатурой файла.
npm install file-type
const { fileTypeFromBuffer } = require('file-type');
async function validateFile(fileBuffer) {
const result =
await fileTypeFromBuffer(fileBuffer);
if (!result) {
return false;
}
return result.mime === 'image/png';
}
const path = require('path');
function validateFile(file) {
const ext =
path.extname(file.originalname);
const mime =
file.mimetype;
return (
ext === '.png' &&
mime === 'image/png'
);
}
const allowed = new Set([
'image/png',
'image/jpeg',
'application/pdf'
]);
function validateMime(mime) {
return (
validator.isMimeType(mime) &&
allowed.has(mime)
);
}
Менее безопасный подход:
const forbidden = [
'application/x-msdownload',
'application/x-sh'
];
Причина — невозможно предусмотреть все опасные типы.
При отправке форм браузер использует:
multipart/form-data
Проверка:
validator.isMimeType('multipart/form-data');
true
function validateJsonRequest(req) {
const contentType =
req.headers['content-type'];
if (!contentType) {
return false;
}
const mime =
contentType.split(';')[0];
return mime === 'application/json';
}
validator.isMimeType('application/xml');
true
Validator.js поддерживает и vendor-specific MIME-типы.
validator.isMimeType(
'application/vnd.api+json'
);
true
Современные API активно используют суффиксы:
application/ld+json
application/problem+json
application/vnd.api+json
Проверка:
validator.isMimeType(
'application/problem+json'
);
true
const mimeTypes = [
'image/png',
'application/json',
'invalid/type/'
];
const result = mimeTypes.map(mime => ({
mime,
valid: validator.isMimeType(mime)
}));
console.log(result);
function validateFiles(files) {
return files.every(file => {
return validator.isMimeType(
file.mimetype
);
});
}
async function uploadFile(_, { file }) {
const { mimetype } = await file;
if (!validator.isMimeType(mimetype)) {
throw new Error('Некорректный MIME');
}
return true;
}
Плохо:
if (filename.endsWith('.png'))
Расширение можно подделать.
Плохо:
req.headers['content-type']
Заголовок легко изменить вручную.
Плохо:
if (validator.isMimeType(mime))
Корректный MIME ещё не означает безопасный файл.
validator.isMimeType(mime)
allowed.has(mime)
fileTypeFromBuffer(buffer)
if (file.size > MAX_SIZE)
ClamAV, VirusTotal, Sandbox и другие решения.
isMimeType() работает очень быстро, поскольку выполняет
только строковую проверку формата.
Даже массовая валидация тысяч MIME-типов обычно не создаёт заметной нагрузки.
isMimeType()Функция:
charset,
boundary);const validator = require('validator');
const allowedMimeTypes = new Set([
'image/png',
'image/jpeg',
'application/pdf'
]);
function validateMime(req, res, next) {
const file = req.file;
if (!file) {
return res.status(400).send(
'Файл отсутствует'
);
}
const mime = file.mimetype;
if (!validator.isMimeType(mime)) {
return res.status(400).send(
'Некорректный MIME'
);
}
if (!allowedMimeTypes.has(mime)) {
return res.status(400).send(
'Тип файла запрещён'
);
}
next();
}
import validator from 'validator';
export function isAllowedMime(
mime,
allowed
) {
if (!mime) {
return false;
}
if (!validator.isMimeType(mime)) {
return false;
}
return allowed.includes(mime);
}
Использование:
const allowed = [
'image/png',
'image/jpeg'
];
isAllowedMime(
'image/png',
allowed
);
true