mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-06 04:02:36 +02:00
The line-by-line web translator reordered words inside `**bold**` spans and misplaced/dropped the markers, producing `**LB` or `****` (empty bold). Mask each `**...**` as ONE atomic sentinel: translate the inner text on its own, then store the whole balanced `**inner**` — the engine never sees the markers and cannot reorder or collapse them. Also harden the anthropic prompt to keep emphasis balanced. Auto-prune: after translating, delete generated docs/ru files whose docs/en source no longer exists (renamed/removed) and drop now-empty dirs, so the tree mirrors docs/en 1:1 (removes the stale development/modules.md and guides/geoip-and-device-detection.md). Bump PROMPT_VERSION to 6 to invalidate the contaminated cache and regenerate docs/ru (0 broken bold spans remaining, aside from pre-existing multi-line bold that spans a soft line break).
321 lines
19 KiB
Markdown
321 lines
19 KiB
Markdown
# Проверка и санитарная обработка входных данных
|
||
|
||
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-комментариев|`<!--` становится `<!--`, `-->` становится `-->`|
|
||
|Нейтрализация тегов скрипта|`<script` (без учета регистра) становится `<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()` перед бизнес-логикой|
|