# Проверка и санитарная обработка входных данных XC_VM использует двухуровневую защиту для входящих данных запроса. Во-первых, передача **глобальная санитарная обработка** удаляет опасный контент со всех PHP суперглобальных объектов во время начальной загрузки, перед запуском любого кода приложения. Во-вторых, уровень **проверка на уровне действий** проверяет наличие обязательных полей перед выполнением бизнес-логики. Оба слоя реализованы в виде `src/Core/Validation/InputValidator.php`. --- ## Глобальный поток санитарной обработки Очистка выполняется автоматически во время начальной загрузки. Когда вызывается `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)) ``` После выполнения этой последовательности все необработанные суперглобальные данные были обработаны на месте, и объединенные/очищенные данные GET+POST доступны через `RequestManager`. Контекст потоковой передачи (`LegacyInitializer::initStreaming()`) выполняет ту же последовательность очистки с использованием класса `Request`, который предоставляет эквивалентные методы для пути начальной загрузки потоковой передачи. ### Чистые глобальные значения(&$rData, $rIteration = 0) Рекурсивно обходит заданный суперглобальный массив и удаляет опасное содержимое. Применяется к `$_GET`, `$_POST`, `$_SESSION`, и `$_COOKIE`. Извлекает следующее из каждого скалярного значения: |Угроза|Узор удален|Замена| | --- | --- | --- | |Ввод нулевого байта|`\0` (chr 0)|удаленный| |Обход пути| `../` |`../` (в кодировке HTML)| |Переопределение RTL (подмена пользовательского интерфейса)| `‮` |удаленный| Рекурсия ограничена 10 уровнями, чтобы предотвратить исчерпание стека из-за глубоко вложенных входных данных. ### Синтаксический анализ выполняется рекурсивно(&$rData, $rInput, $rIteration = 0) Рекурсивно обрабатывает данные GET и POST, применяя очистку ключей и значений к каждому листу. Для массивов выполняется рекурсия на глубину до 20 уровней. Для скалярных значений применяется `parseCleanKey()` к ключу и `parseCleanValue()` к значению. Объединенный результат (сначала ПОЛУЧИТЬ, затем опубликовать с наложением) сохраняется в `RequestManager` для использования на протяжении всего жизненного цикла запроса. ### parseCleanKey($rKey) Очищает ключи массива, чтобы предотвратить внедрение с помощью имен ключей: 1. URL-расшифровывает, а HTML-экранирует ключ (`htmlspecialchars(urldecode(...))`) 2. Удаляет последовательности с двумя точками (`..` -> `''`) 3. Strips `__dunder__`-маркеры стиля с помощью регулярного выражения 4. Проверяет соответствие допустимому набору символов: символы word, точки, дефисы, подчеркивания ### Значение parseCleanValue($rValue) Выполняет очистку скалярных значений за несколько проходов: |Шаг|Что он делает| | --- | --- | |Неэкранированный пейзаж|`stripslashes()` и вернуть ` ` в исходное положение| |Нормализовать новые строки|Преобразовать `\r\n`, `\n\r`, `\r` в `\n`| |Защита HTML-комментариев|`` становится `-->`| |Нейтрализация тегов скрипта|` STATUS_INVALID_INPUT, 'data' => $data] return $error; } ``` ### Подтверждающие идентификаторы($ids) ```php InputValidator::confirmIDs($ids) // untyped params/return; yields a filtered array of positive int IDs ``` Фильтрует массив, чтобы он содержал только целые положительные идентификаторы. Любое значение, в котором пропущено значение `intval($id) <= 0`. Широко используется в кодовой базе (более 30 сайтов для звонков) везде, где необходимо очистить списки идентификаторов, предоставленные пользователем, перед запросами к базе данных. ```php $safeIds = InputValidator::confirmIDs($userSuppliedIds); // [1, 42, 7] -- negative, zero, and non-numeric values removed ``` --- ## Ссылка на действие проверки В методе `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| #### Фильмы / VOD |Действие|Обязательные для заполнения поля|Записи| | --- | --- | --- | | `processMovie` |флаг `stream_display_name` ИЛИ `review` ИЛИ `$_FILES['m3u_file']`|Те же правила, что и в processStream| #### Сериалы и эпизоды |Действие|Обязательные для заполнения поля|Записи| | --- | --- | --- | | `processSeries` | `title` |Требуется указать название серии| | `processEpisode` |`series` (непустой) И `season_num` (числовой) И (`multi` флаг ИЛИ `episode` числовой)|Сложная многопутевая проверка| ### Организация #### Букеты |Действие|Обязательные для заполнения поля|Записи| | --- | --- | --- | | `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| ### Папки с записями и просмотром |Действие|Обязательные для заполнения поля|Записи| | --- | --- | --- | | `scheduleRecording` |`title`, `source_id`|Оба необходимых| | `processWatchFolder` |`folder_type`, `selected_path`, `server_id`|Все три необходимых| ### Массовые операции (полезная нагрузка в виде массива JSON) Для всех массовых операций в указанном поле требуется массив в кодировке JSON. Поле должно быть преобразовано в допустимый массив 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` | --- ## Аварийное поведение по умолчанию Действия, явно не указанные в инструкции `switch`, попадают в `return true`, что означает, что они всегда проходят проверку. Это сделано намеренно - эти действия либо не содержат обязательных полей на уровне gate, либо выполняют свою собственную проверку на более глубоком уровне бизнес-логики. **Явно выполняемые сквозные действия** (указан в переключателе как `return true`): - `processUser` - `processLine` - `processHMAC` - `editAdminProfile` - `editSettings` - `editBackupSettings` - `editCacheCron` - `editPlexSettings` - `editWatchSettings` - `processPlexSync` - `processLogin` - `submitTicket` **Неявно передаваемые действия** (вообще отсутствует в переключателе, используется значение по умолчанию `return true`): Любая строка действия, не соответствующая `case`, также вернет значение `true`. Если для нового действия требуется стробирование ввода, необходимо явно добавить регистр. --- ## Используемые шаблоны проверки Метод `validate()` последовательно использует небольшой набор шаблонов: |Шаблон|Цель|Пример| | --- | --- | --- | | `!empty($rData['field'])` |Обязательное скалярное поле (ненулевое, непустое, ненулевое значение)| `!empty($rData['bouquet_name'])` | | `is_numeric($rData['field'] ?? null)` |Числовая проверка с резервным копированием, безопасным для null| `is_numeric($rData['season_num'] ?? null)` | | `is_array(json_decode($rData['field'] ?? '', true))` |Строка JSON, которую необходимо декодировать в массив| `is_array(json_decode($rData['streams'] ?? '', true))` | | `isset($rData['field'])` |Проверка наличия поля (значение может быть пустым/ложным)| `isset($rData['review'])` | | `isset($_FILES['field'])` |Проверка наличия загружаемого файла| `isset($_FILES['m3u_file'])` | |ИЛИ условия|Многопутевая проверка (удовлетворяет любой из путей)|`!пусто($rData['имя_потока']) \|\|isset($rData['обзор']) \|\|isset($_FILES['m3u_file'])`| --- ## Добавление проверки для нового действия Добавьте `case` к `switch` в `src/Core/Validation/InputValidator.php`: ```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'])` в качестве условия ИЛИ. - Если действие не требует проверки на уровне шлюза, добавьте его в явный сквозной блок с `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()` перед бизнес-логикой|