Overhaul the Docsify documentation (English + Russian) so it matches the current codebase and follows one consistent pattern. Content accuracy (post-migration): - Rewrite development/autoloader.md to PSR-4 / Composer (the old XC_Autoloader scanner, igbinary tmp/cache/autoload_map and registerDirectories are gone). - PascalCase every source path (src/core -> src/Core, domain/Stream, cli/Commands, public/Controllers, Infrastructure/Redis, ...) across all docs. - Replace the removed autoload.php references with vendor/autoload.php (build_system, bootstrap-contexts, error-handling, modules). - ssl-generation: note that the installer now auto-generates a unique self-signed certificate before Nginx starts. Common pattern (Clean & uniform): - Strip emoji from headings; remove the in-page Navigation blocks (the Docsify sidebar already provides navigation). - One H1 + intro per doc; uniform "Related files" / "Связанные файлы" section, added to the code-centric docs that lacked it. Structure: - Remove the empty stray docs/api/; move updates_checklist.md into builds/; link the previously-orphaned ucs-integration.md. - Regroup the sidebars (split the oversized guides group into Developer Guides / Security & Access / Integrations; fold builds into Build & Release). Augment: - dev-workflow: Local Setup (make dev-tools) + Quality Checks (phpstan, cs, gates). - build_system: Composer Dependencies section (committed prod-only vendor, committed lock, dev tools via composer install, no build-time vendor step). en/ru parity: - Apply the same structure, fixes and pattern to docs/ru/ (translated), including a new Russian ucs-integration.md. The en and ru file sets are now identical.
17 KiB
Валидация и санитизация ввода
XC_VM использует двухуровневую защиту входящих данных запроса. Сначала глобальная санитизация удаляет опасный контент из всех PHP-суперглобалов во время bootstrap, до того как заработает прикладной код. Затем валидация на уровне действий проверяет наличие обязательных полей перед выполнением бизнес-логики.
Оба слоя реализованы в src/Core/Validation/InputValidator.php.
Поток глобальной санитизации
Санитизация запускается автоматически при bootstrap. Когда вызывается LegacyInitializer::initCore() (в src/Core/Init/LegacyInitializer.php), он выполняет следующие шаги до любого контроллера или сервиса:
LegacyInitializer::initCore()
|
+-- InputValidator::cleanGlobals($_GET)
+-- InputValidator::cleanGlobals($_POST)
+-- InputValidator::cleanGlobals($_SESSION)
+-- InputValidator::cleanGlobals($_COOKIE)
|
+-- $input = InputValidator::parseIncomingRecursively($_GET)
+-- RequestManager::set(InputValidator::parseIncomingRecursively($_POST, $input))
После этой последовательности все сырые суперглобалы санитизированы in-place, а объединённые/очищенные данные GET+POST доступны через RequestManager.
Стриминговый контекст (LegacyInitializer::initStreaming()) выполняет ту же последовательность санитизации, используя класс Request, который предоставляет эквивалентные методы для bootstrap-пути стриминга.
cleanGlobals(&$rData, $rIteration = 0)
Рекурсивно обходит заданный массив суперглобала и удаляет опасный контент. Применяется к $_GET, $_POST, $_SESSION и $_COOKIE.
Удаляет следующее из каждого скалярного значения:
| Угроза | Удаляемый паттерн | Замена |
|---|---|---|
| Null byte injection | \0 (chr 0) |
удаляется |
| Path traversal | ../ |
../ (HTML-encoded) |
| RTL override (UI spoofing) | ‮ |
удаляется |
Рекурсия ограничена 10 уровнями для предотвращения исчерпания стека при глубоко вложенном вводе.
parseIncomingRecursively(&$rData, $rInput, $rIteration = 0)
Рекурсивно обходит данные GET и POST, применяя санитизацию ключей и значений к каждому листу. Для массивов рекурсирует до 20 уровней. Для скалярных значений применяет parseCleanKey() к ключу и parseCleanValue() к значению.
Объединённый результат (сначала GET, затем POST поверх него) сохраняется в RequestManager для использования в течение жизненного цикла запроса.
parseCleanKey($rKey)
Санитизирует ключи массивов для предотвращения инъекций через имена ключей:
- URL-декодирует и HTML-экранирует ключ (
htmlspecialchars(urldecode(...))) - Удаляет последовательности двойных точек (
..->'') - Удаляет маркеры в стиле
__dunder__через regex - Валидирует по разрешённому набору символов: словесные символы, точки, дефисы, подчёркивания
parseCleanValue($rValue)
Санитизирует скалярные значения в несколько проходов:
| Шаг | Что делает |
|---|---|
| Unescape | stripslashes() и восстановление   в пробел |
| Нормализация переводов строк | Конвертирует \r\n, \n\r, \r в \n |
| Защита HTML-комментариев | <!-- становится <!--, --> становится --> |
| Нейтрализация script-тегов | <script (регистронезависимо) становится <script |
| Нормализация сущностей | Исправление двойного-кодирования и некорректных числовых сущностей |
| Trim | Удаление ведущих/завершающих пробелов |
Валидация на уровне действий
validate()
InputValidator::validate(string $rAction, array $rData): bool
Проверяет наличие минимально требуемых полей для заданного действия. Возвращает true, если данные приемлемы, и false, если обязательные поля отсутствуют или некорректны. Контроллеры должны вызывать её перед передачей данных в слой сервисов/репозиториев.
if (!InputValidator::validate($action, $data)) {
// отклонить с ошибкой валидации
}
validateOrFail()
InputValidator::validateOrFail(string $rAction, array $rData): ?array
Удобная обёртка над validate(). Возвращает null, если данные валидны, или массив с ошибкой, если валидация не прошла:
$error = InputValidator::validateOrFail($action, $data);
if ($error !== null) {
// $error = ['status' => STATUS_INVALID_INPUT, 'data' => $data]
return $error;
}
confirmIDs($ids)
InputValidator::confirmIDs(array $ids): array
Фильтрует массив, оставляя только положительные целочисленные ID. Любое значение, где intval($id) <= 0, отбрасывается. Используется широко по кодовой базе (30+ мест вызова) везде, где пользовательские списки ID должны быть санитизированы перед запросами к базе.
$safeIds = InputValidator::confirmIDs($userSuppliedIds);
// [1, 42, 7] — отрицательные, ноль и нечисловые значения удалены
Справочник действий валидации
Метод validate() использует switch по имени действия. Действия сгруппированы по функциональным областям ниже.
Управление контентом
Потоки и каналы
| Действие | Обязательные поля | Заметки |
|---|---|---|
processStream |
stream_display_name ИЛИ флаг review ИЛИ $_FILES['m3u_file'] |
Любое из трёх удовлетворяет валидации |
processChannel |
stream_display_name ИЛИ флаг review ИЛИ $_FILES['m3u_file'] |
Те же правила, что у processStream |
processRadio |
stream_display_name ИЛИ флаг review ИЛИ $_FILES['m3u_file'] |
Те же правила, что у processStream |
Movies / VOD
| Действие | Обязательные поля | Заметки |
|---|---|---|
processMovie |
stream_display_name ИЛИ флаг review ИЛИ $_FILES['m3u_file'] |
Те же правила, что у processStream |
Сериалы и эпизоды
| Действие | Обязательные поля | Заметки |
|---|---|---|
processSeries |
title |
Имя сериала обязательно |
processEpisode |
series (непустое) И season_num (numeric) И (флаг multi ИЛИ episode numeric) |
Сложная мульти-путевая валидация |
Организация
Букеты
| Действие | Обязательные поля | Заметки |
|---|---|---|
processBouquet |
bouquet_name |
Обязательный скаляр |
reorderBouquet |
stream_order_array |
Должен декодироваться в JSON-массив |
sortBouquets |
bouquet_order_array |
Должен декодироваться в JSON-массив |
Категории
| Действие | Обязательные поля | Заметки |
|---|---|---|
processCategory |
category_name, category_type |
Оба обязательны |
orderCategories |
categories |
Должен декодироваться в JSON-массив |
Группы и коды
| Действие | Обязательные поля | Заметки |
|---|---|---|
processGroup |
group_name |
Обязательный скаляр |
processGroupLegacy |
group_name |
То же, что processGroup |
processCode |
code |
Обязательный скаляр |
processPackage |
package_name |
Обязательный скаляр |
EPG
| Действие | Обязательные поля | Заметки |
|---|---|---|
processEPG |
epg_name, epg_file |
Оба обязательны |
Устройства и линии
| Действие | Обязательные поля | Заметки |
|---|---|---|
processMAG |
mac |
MAC-адрес обязателен |
processEnigma |
mac |
MAC-адрес обязателен |
setChannelOrder |
stream_order_array |
Должен декодироваться в JSON-массив |
Профили
| Действие | Обязательные поля | Заметки |
|---|---|---|
processProfile |
profile_name |
Обязательный скаляр |
Провайдеры
| Действие | Обязательные поля | Заметки |
|---|---|---|
processProvider |
ip, port, username, password, name |
Все пять обязательны |
processISP |
isp |
Обязательный скаляр |
processUA |
user_agent |
Обязательный скаляр |
Безопасность
| Действие | Обязательные поля | Заметки |
|---|---|---|
blockIP |
ip |
IP-адрес обязателен |
processRTMPIP |
ip |
IP-адрес обязателен |
Управление серверами
| Действие | Обязательные поля | Заметки |
|---|---|---|
processServer |
server_name, server_ip |
Оба обязательны |
processProxy |
server_name, server_ip |
То же, что processServer |
installServer |
ssh_port, root_password |
Оба обязательны |
moveStreams |
content_type, source_server, replacement_server |
Все три обязательны |
replaceDNS |
old_dns, new_dns |
Оба обязательны |
orderServers |
server_order |
Должен декодироваться в JSON-массив |
Записи и Watch Folders
| Действие | Обязательные поля | Заметки |
|---|---|---|
scheduleRecording |
title, source_id |
Оба обязательны |
processWatchFolder |
folder_type, selected_path, server_id |
Все три обязательны |
Массовые операции (JSON-массивы)
Все массовые операции требуют JSON-encoded массив в указанном поле. Поле должно декодироваться в валидный PHP-массив.
| Действие | JSON-поле |
|---|---|
massEditEpisodes |
streams |
massEditMovies |
streams |
massEditRadios |
streams |
massEditStreams |
streams |
massEditChannels |
streams |
massDeleteStreams |
streams |
massEditSeries |
series |
massDeleteSeries |
series |
massEditLines |
users_selected |
massEditUsers |
users_selected |
massEditMags |
devices_selected |
massEditEnigmas |
devices_selected |
massDeleteMovies |
movies |
massDeleteLines |
lines |
massDeleteUsers |
users |
massDeleteStations |
radios |
massDeleteMags |
mags |
massDeleteEnigmas |
enigmas |
massDeleteEpisodes |
episodes |
Поведение по умолчанию (fallthrough)
Действия, явно не перечисленные в switch, проваливаются на return true, то есть всегда проходят валидацию. Это намеренно — у этих действий либо нет обязательных полей на уровне гейта, либо они выполняют собственную валидацию глубже в слое бизнес-логики.
Действия с явным пропуском (в switch с return true):
processUserprocessLineprocessHMACeditAdminProfileeditSettingseditBackupSettingseditCacheCroneditPlexSettingseditWatchSettingsprocessPlexSyncprocessLoginsubmitTicket
Действия с неявным пропуском (вообще не в switch, попадают в default return true):
Любая строка действия, не совпадающая с case, также вернёт true. Если новому действию нужен input-гейт, case должен быть добавлен явно.
Используемые паттерны валидации
Метод validate() использует небольшой набор паттернов последовательно:
| Паттерн | Назначение | Пример |
|---|---|---|
!empty($rData['field']) |
Обязательное скалярное поле (не null, не пустое, не ноль) | !empty($rData['bouquet_name']) |
is_numeric($rData['field'] ?? null) |
Числовая валидация с null-safe fallback | is_numeric($rData['season_num'] ?? null) |
is_array(json_decode($rData['field'] ?? '', true)) |
JSON-строка, декодируемая в массив | is_array(json_decode($rData['streams'] ?? '', true)) |
isset($rData['field']) |
Проверка наличия поля (значение может быть пустым/falsy) | isset($rData['review']) |
isset($_FILES['field']) |
Проверка наличия загруженного файла | isset($_FILES['m3u_file']) |
| OR-условия | Мульти-путевая валидация (любой путь удовлетворяет) | !empty($rData['stream_display_name']) || isset($rData['review']) || isset($_FILES['m3u_file']) |
Добавление валидации для нового действия
Добавьте case в switch в src/Core/Validation/InputValidator.php:
case 'myNewAction':
return !empty($rData['required_field'])
&& is_numeric($rData['numeric_field'] ?? null);
Рекомендации:
- Валидируйте только минимально необходимые входные данные на этом слое. Доменные правила (валидация формата, бизнес-ограничения, проверки уникальности) держите в сервисном слое.
- Используйте
!empty()для обязательных скаляров,is_numeric()для числовых полей иis_array(json_decode(..., true))для JSON-массивов. - Для действий, принимающих загрузку файлов как альтернативу полям формы, включите
isset($_FILES['field'])как OR-условие. - Если действию не нужна валидация на уровне гейта, добавьте его в блок явного пропуска с
return true, чтобы будущим мейнтейнерам было понятно, что пропуск намеренный, а не случайный.
Связанные файлы
| Файл | Назначение |
|---|---|
src/Core/Validation/InputValidator.php |
Вся логика санитизации и валидации |
src/Core/Init/LegacyInitializer.php |
Bootstrap-вызыватель, запускающий санитизацию через initCore() |
src/Core/Http/RequestManager.php |
Хранит санитизированные, объединённые данные GET+POST |
src/Public/Controllers/ |
Контроллеры, вызывающие validate() / validateOrFail() перед бизнес-логикой |