Files
XC_VM/docs/ru/guides/input-validation.md
T

321 lines
19 KiB
Markdown
Raw Normal View History

# Проверка и санитарная обработка входных данных
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-комментариев|`<!--` становится `&#60;&#33;--`, `-->` становится `--&#62;`|
|Нейтрализация тегов скрипта|`<script` (без учета регистра) становится `&#60;script`|
|Нормализация объекта|Исправлены объекты с двойным кодированием и искаженные числовые объекты|
|Отделка|Начальные и конечные пробелы удалены|
---
## Проверка на уровне действий
### проверить()
```php
InputValidator::validate(string $rAction, array $rData): bool
```
Проверяет наличие минимально необходимых полей для данного действия. Возвращает `true`, если данные приемлемы, `false`, если необходимые поля отсутствуют или оформлены неправильно. Контроллеры должны вызывать это перед отправкой данных на уровни службы/хранилища.
```php
if (!InputValidator::validate($action, $data)) {
// reject with validation error
}
```
### validateOrFail()
```php
InputValidator::validateOrFail(string $rAction, array $rData): ?array
```
Удобная оболочка для `validate()`. Возвращает `null`, если данные верны, или массив ошибок, если проверка не удалась:
```php
$error = InputValidator::validateOrFail($action, $data);
if ($error !== null) {
// $error = ['status' => 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()` перед бизнес-логикой|