Работа с расширениями файлов

Библиотека Validator.js предназначена для валидации строковых данных и активно используется в Node.js-приложениях, REST API, формах загрузки файлов и системах хранения контента. При работе с файлами одной из типичных задач становится проверка расширения: допустим ли формат файла, соответствует ли он политике безопасности приложения и не пытается ли пользователь загрузить запрещённый тип данных.

Поскольку Validator.js работает со строками, проверка расширений обычно строится вокруг имени файла, MIME-типа или URL.


Установка Validator.js

Установка через npm

npm install validator

Подключение в CommonJS

const validator = require('validator');

Подключение в ES Modules

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.js

import 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'));

Проверка через Validator.js

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

Проверка MIME-типа

Расширение файла может быть подделано, поэтому в production-системах дополнительно проверяется MIME.

Пример объекта файла

const file = {
    originalname: 'photo.jpg',
    mimetype: 'image/jpeg'
};

Проверка MIME

const isValidMime = validator.matches(
    file.mimetype,
    /^image\/(jpeg|png|gif|webp)$/
);

Совмещение проверки расширения и MIME

Надёжная схема

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;
}

Валидация URL файлов

Часто необходимо проверять ссылки на файлы.

Проверка URL

const url = 'https://site.com/image.png';

const isUrl = validator.isURL(url);

console.log(isUrl);

Проверка расширения внутри URL

const isImageUrl = validator.matches(
    url,
    /\.(jpg|jpeg|png|gif)$/i
);

Проверка query-параметров в URL

Некоторые URL содержат параметры:

https://site.com/image.png?size=large

Обычная проверка может не сработать.

Решение

const pattern = /\.(jpg|jpeg|png)(\?.*)?$/i;

validator.matches(
    'https://site.com/image.png?size=large',
    pattern
);

Валидация Base64-файлов

При загрузке через 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);

Проверка в Express.js

Middleware для загрузки файлов

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();
}

Проверка файлов в Multer

Настройка fileFilter

import 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 содержит методы очистки строк.

Экранирование HTML

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

Необходимо:

  • проверять MIME
  • ограничивать список типов
  • анализировать содержимое файла
  • ограничивать размер файла

Проверка размера файла

Validator.js напрямую не проверяет размер файлов, но может валидировать строковые значения.

Пример

function validateFileSize(size) {

    return validator.isInt(
        String(size),
        {
            min: 1,
            max: 5 * 1024 * 1024
        }
    );
}

Комплексная проверка файла

Production-пример

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
);

Дополнительные меры

  • ограничение размера архива
  • проверка количества файлов
  • запрет исполняемых файлов внутри архива
  • защита от Zip Bomb

Проверка видеофайлов

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-символов в именах файлов

Некоторые файловые системы поддерживают Unicode.

Пример

const fileName = 'фото.png';

Проверка Unicode

const pattern =
    /^[\p{L}\p{N}_\-. ]+$/u;

validator.matches(fileName, pattern);

Проверка путей файлов

Запрет path traversal

Опасный путь:

../. ./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('Изображение допустимо');
}

Проверка CSV-файлов

const csvPattern = /\.csv$/i;

validator.matches(
    'report.csv',
    csvPattern
);

Проверка JSON-файлов

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;
}