Библиотека Validator.js предназначена для валидации строковых данных и активно используется в Node.js-приложениях, REST API, формах загрузки файлов и системах хранения контента. При работе с файлами одной из типичных задач становится проверка расширения: допустим ли формат файла, соответствует ли он политике безопасности приложения и не пытается ли пользователь загрузить запрещённый тип данных.
Поскольку Validator.js работает со строками, проверка расширений обычно строится вокруг имени файла, MIME-типа или URL.
npm install validator
const validator = require('validator');
import validator from 'validator';
matches()Validator.js не содержит специализированного метода вроде
isFileExtension(), поэтому основной инструмент — метод
matches(), принимающий регулярное выражение.
import validator from 'validator';
const fileName = 'photo.jpg';
const isValid = validator.matches(fileName, /\.(jpg|jpeg|png)$/i);
console.log(isValid);
\.(jpg|jpeg|png)$
\. — обязательная точка перед расширением(jpg|jpeg|png) — список допустимых расширений$ — конец строкиi — игнорирование регистраПоддерживаются:
photo.jpg
photo.JPG
photo.Jpeg
const imagePattern = /\.(jpg|jpeg|png|gif|webp|svg)$/i;
validator.matches('avatar.webp', imagePattern);
const documentPattern = /\.(pdf|doc|docx|xls|xlsx)$/i;
validator.matches('report.pdf', documentPattern);
const archivePattern = /\.(zip|rar|7z|tar|gz)$/i;
validator.matches('backup.zip', archivePattern);
Иногда требуется валидировать исключительно расширение.
const extension = 'pdf';
const isValid = validator.matches(extension, /^(pdf|docx|txt)$/i);
console.log(isValid);
Validator.js не извлекает расширения самостоятельно, поэтому обычно используется стандартный JavaScript.
split()const fileName = 'archive.tar.gz';
const extension = fileName.split('.').pop();
console.log(extension);
path.extname()
в Node.jsimport path from 'path';
const fileName = 'photo.png';
const extension = path.extname(fileName);
console.log(extension);
Результат:
.png
path.extname() и Validator.jsНаиболее безопасный и читаемый вариант.
import path from 'path';
import validator from 'validator';
const fileName = 'document.pdf';
const ext = path.extname(fileName);
const isAllowed = validator.matches(
ext,
/^\.(pdf|doc|docx)$/i
);
console.log(isAllowed);
Белый список — предпочтительный подход при загрузке файлов.
const allowedExtensions = [
'jpg',
'jpeg',
'png',
'webp'
];
function validateFile(fileName) {
const ext = fileName.split('.').pop().toLowerCase();
return allowedExtensions.includes(ext);
}
console.log(validateFile('image.png'));
function validateFile(fileName) {
return validator.matches(
fileName,
/\.(jpg|jpeg|png|webp)$/i
);
}
Иногда требуется блокировать опасные форматы.
const blockedPattern = /\.(exe|bat|cmd|sh|msi)$/i;
const isBlocked = validator.matches(
'virus.exe',
blockedPattern
);
console.log(isBlocked);
function isSafeFile(fileName) {
return !validator.matches(
fileName,
/\.(exe|bat|cmd|sh)$/i
);
}
Опасный сценарий:
photo.jpg.exe
Наивная проверка может пропустить такой файл.
validator.matches(
'photo.jpg.exe',
/\.jpg/i
);
Результат будет true, хотя файл исполняемый.
validator.matches(
'photo.jpg.exe',
/\.(jpg|png)$/i
);
Результат:
false
Проверка конца строки через $ обязательна.
В Unix-подобных системах скрытые файлы начинаются с точки.
.env
.gitignore
const hiddenPattern = /^\./;
validator.matches('.env', hiddenPattern);
function isAllowedFile(fileName) {
return !validator.matches(fileName, /^\./);
}
const fileName = 'README';
const hasExtension = validator.matches(
fileName,
/\.[a-z0-9]+$/i
);
console.log(hasExtension);
Validator.js содержит встроенный метод isLength().
const fileName = 'very_long_document_name.pdf';
const isValid = validator.isLength(fileName, {
min: 1,
max: 100
});
const pattern = /^[a-z0-9_\-.]+$/i;
validator.matches(
'photo_01.png',
pattern
);
validator.matches(
'photo<script>.png',
/^[a-z0-9_\-.]+$/i
);
Результат:
false
Расширение файла может быть подделано, поэтому в production-системах дополнительно проверяется MIME.
const file = {
originalname: 'photo.jpg',
mimetype: 'image/jpeg'
};
const isValidMime = validator.matches(
file.mimetype,
/^image\/(jpeg|png|gif|webp)$/
);
function validateImage(file) {
const validExtension = validator.matches(
file.originalname,
/\.(jpg|jpeg|png|webp)$/i
);
const validMime = validator.matches(
file.mimetype,
/^image\/(jpeg|png|webp)$/
);
return validExtension && validMime;
}
Часто необходимо проверять ссылки на файлы.
const url = 'https://site.com/image.png';
const isUrl = validator.isURL(url);
console.log(isUrl);
const isImageUrl = validator.matches(
url,
/\.(jpg|jpeg|png|gif)$/i
);
Некоторые URL содержат параметры:
https://site.com/image.png?size=large
Обычная проверка может не сработать.
const pattern = /\.(jpg|jpeg|png)(\?.*)?$/i;
validator.matches(
'https://site.com/image.png?size=large',
pattern
);
При загрузке через API файлы иногда передаются в Base64.
data:image/png;base64,iVBORw0KGgoAAAANS...
const base64Pattern =
/^data:image\/(png|jpeg|jpg);base64,/i;
validator.matches(data, base64Pattern);
import path from 'path';
import validator from 'validator';
function validateExtension(fileName, allowed) {
const ext = path.extname(fileName)
.replace('.', '')
.toLowerCase();
const pattern = new RegExp(
`^(${allowed.join('|')})$`,
'i'
);
return validator.matches(ext, pattern);
}
const result = validateExtension(
'avatar.png',
['jpg', 'jpeg', 'png']
);
console.log(result);
import validator from 'validator';
function validateUpload(req, res, next) {
const file = req.file;
if (!file) {
return res.status(400).json({
error: 'Файл не загружен'
});
}
const isValid = validator.matches(
file.originalname,
/\.(jpg|jpeg|png)$/i
);
if (!isValid) {
return res.status(400).json({
error: 'Недопустимый формат файла'
});
}
next();
}
fileFilterimport multer from 'multer';
import validator from 'validator';
const upload = multer({
fileFilter(req, file, cb) {
const isValid = validator.matches(
file.originalname,
/\.(png|jpg|jpeg)$/i
);
if (isValid) {
cb(null, true);
} else {
cb(new Error('Недопустимый формат'));
}
}
});
Validator.js содержит методы очистки строк.
const fileName = '<script>alert(1)</script>.png';
const safeName = validator.escape(fileName);
console.log(safeName);
const fileName = ' photo.png ';
const trimmed = validator.trim(fileName);
console.log(trimmed);
const ext = 'JPG';
const normalized = validator
.trim(ext)
.toLowerCase();
console.log(normalized);
Неправильно:
/\.jpg/
Правильно:
/\.jpg$/
iНеправильно:
/\.(jpg|png)$/
Файл:
PHOTO.JPG
Результат:
false
Правильно:
/\.(jpg|png)$/i
Опасно:
virus.jpg.exe
Необходимо:
Validator.js напрямую не проверяет размер файлов, но может валидировать строковые значения.
function validateFileSize(size) {
return validator.isInt(
String(size),
{
min: 1,
max: 5 * 1024 * 1024
}
);
}
import validator from 'validator';
function validateFile(file) {
if (!file) {
return false;
}
const validName = validator.matches(
file.originalname,
/^[a-z0-9_\-. ]+$/i
);
const validExtension = validator.matches(
file.originalname,
/\.(jpg|jpeg|png|webp)$/i
);
const validMime = validator.matches(
file.mimetype,
/^image\/(jpeg|png|webp)$/
);
const validSize = validator.isInt(
String(file.size),
{
min: 1,
max: 5 * 1024 * 1024
}
);
return (
validName &&
validExtension &&
validMime &&
validSize
);
}
Архивы часто используются для атак через вредоносное содержимое.
const isArchive = validator.matches(
file.originalname,
/\.(zip|rar|7z)$/i
);
const videoPattern =
/\.(mp4|avi|mov|mkv|webm)$/i;
validator.matches(
'movie.mp4',
videoPattern
);
const audioPattern =
/\.(mp3|wav|flac|aac|ogg)$/i;
validator.matches(
'track.mp3',
audioPattern
);
const codePattern =
/\.(js|ts|jsx|tsx|json)$/i;
const blockedPattern =
/\.(php|exe|sh|bat)$/i;
function createExtensionPattern(exts) {
return new RegExp(
`\\.(${exts.join('|')})$`,
'i'
);
}
const pattern = createExtensionPattern([
'jpg',
'png',
'gif'
]);
validator.matches(
'image.png',
pattern
);
Некоторые файловые системы поддерживают Unicode.
const fileName = 'фото.png';
const pattern =
/^[\p{L}\p{N}_\-. ]+$/u;
validator.matches(fileName, pattern);
Опасный путь:
../. ./etc/passwd
const traversalPattern = /\.\.\//;
const isTraversal = validator.matches(
'../. ./etc/passwd',
traversalPattern
);
function isSafePath(path) {
return !validator.matches(
path,
/(\.\.\/|\\\.\.\\)/
);
}
function isImage(fileName) {
return validator.matches(
fileName,
/\.(jpg|jpeg|png|gif|webp)$/i
);
}
if (isImage('avatar.png')) {
console.log('Изображение допустимо');
}
const csvPattern = /\.csv$/i;
validator.matches(
'report.csv',
csvPattern
);
const jsonPattern = /\.json$/i;
validator.matches(
'config.json',
jsonPattern
);
const configPattern =
/\.(env|ini|yaml|yml|toml)$/i;
export const FILE_TYPES = {
images: [
'jpg',
'jpeg',
'png',
'webp'
],
documents: [
'pdf',
'docx',
'txt'
],
archives: [
'zip',
'rar'
]
};
class FileValidator {
static validateImage(fileName) {
return validator.matches(
fileName,
/\.(jpg|jpeg|png|webp)$/i
);
}
static validateDocument(fileName) {
return validator.matches(
fileName,
/\.(pdf|doc|docx)$/i
);
}
}
const namePattern =
/^[a-z0-9_\- ]+$/i;
const extensionPattern =
/^(jpg|png|pdf)$/i;
import path from 'path';
function validate(fileName) {
const parsed = path.parse(fileName);
const validName = validator.matches(
parsed.name,
namePattern
);
const validExt = validator.matches(
parsed.ext.replace('.', ''),
extensionPattern
);
return validName && validExt;
}