# Как PHP обработва качването на файлове

> Как PHP обработва вътрешно качването на файлове: multipart/form-data, временни файлове в upload_tmp_dir, масивът $_FILES, ограничения и кодове за грешки.

Източник: https://phpsos.de/bg/php/datei-uploads-intern/  
Актуалност: 25.10.2026  
Доставчик: PHP SOS, Спешна помощ за PHP и сигурност на сървъри. Незабавна помощ по WhatsApp: https://wa.me/491736494914?text=%D0%97%D0%B4%D1%80%D0%B0%D0%B2%D0%B5%D0%B9%D1%82%D0%B5%2C%20PHP%20SOS%2C%20%D0%B8%D0%BC%D0%B0%D0%BC%D0%B5%20%D0%BD%D1%83%D0%B6%D0%B4%D0%B0%20%D0%BE%D1%82%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%20%D1%81%20%D0%BD%D0%B0%D1%88%D0%B8%D1%8F%20PHP%20%D1%81%D1%8A%D1%80%D0%B2%D1%8A%D1%80.%20%D0%A4%D0%B8%D1%80%D0%BC%D0%B0%3A%20%E2%80%A6%20%D0%A3%D0%B5%D0%B1%D1%81%D0%B0%D0%B9%D1%82%3A%20%E2%80%A6

**Накратко:** Когато браузърът качва файл, той изпраща POST заявка във формат `multipart/form-data`, в която полетата на формата и съдържанието на файловете стоят като отделни части една след друга. PHP сам разчленява тази заявка, записва всяка файлова част във временен файл в `upload_tmp_dir`, вписва данните в `$_FILES` и едва тогава стартира Вашия скрипт. Скриптът премества файла с `move_uploaded_file()`; всичко, което не е преместено, PHP изтрива в края на заявката.

## Пътят на един файл от браузъра до скрипта

За вземащите решения най-важното е следното: качването не е процес само на приложението. Голяма част от работата се извършва в интерпретатора на PHP, преди кодът на CMS или на магазина изобщо да участва. Затова грешки в тази част на PHP засягат всеки уебсайт, независимо дали има форма за качване.

Последователността накратко:

1. Формата с `method="POST"` и `enctype="multipart/form-data"` се изпраща.
2. Уеб сървърът приема заявката и я предава на PHP, според режима на работа през PHP-FPM, mod_php или друг интерфейс.
3. PHP проверява ограничения като `post_max_size` и разчленява съдържанието по разделителна последователност, т.нар. boundary.
4. Обикновените полета попадат в `$_POST`, файловите части се записват във временни файлове и се описват в `$_FILES`.
5. Едва сега скриптът стартира, проверява файла и го премества на целевото място.
6. В края на заявката PHP изтрива всички временни файлове, които не са преместени.

## multipart/form-data: структурата на една заявка за качване

Заявката за качване е чист текст с вградени файлови данни. Хедърът `Content-Type` назовава boundary, а в съдържанието тази последователност от знаци разделя отделните части. Всяка част има собствени заглавни редове с име на полето и евентуално име на файла. Силно съкратен, безобиден пример:

```http
POST /bewerbung HTTP/1.1
Content-Type: multipart/form-data; boundary=----trenner42

------trenner42
Content-Disposition: form-data; name="vorname"

Erika
------trenner42
Content-Disposition: form-data; name="lebenslauf"; filename="lebenslauf.pdf"
Content-Type: application/pdf

%PDF-1.7 ... Dateiinhalt ...
------trenner42--
```

Всички данни в този текст идват от подателя, включително името на файла и `Content-Type` на частта. PHP ги вписва в `$_FILES`, но не проверява достоверността им.

## Масивът $_FILES

За поле с име `lebenslauf` `$_FILES['lebenslauf']` съдържа според ръководството на PHP следните ключове:

| Ключ | Съдържание | Заслужава ли доверие? |
|---|---|---|
| `name` | първоначалното име на файла при подателя | не |
| `full_path` | път, съобщен от браузъра, от PHP 8.1.0 | не |
| `type` | MIME тип според браузъра, непроверен от PHP | не |
| `size` | размер в байтове | определя се от PHP |
| `tmp_name` | път на временния файл на сървъра | задава се от PHP |
| `error` | код за грешка при обработката на качването | задава се от PHP |

При полета с квадратни скоби в името, например `bilder[]`, всеки ключ съдържа масив с по един запис за файл. Приложението тогава обхожда например `$_FILES['bilder']['error']` и през същия индекс достъпва `tmp_name` и `name`.

Една форма може допълнително да изпрати скрито поле `MAX_FILE_SIZE` пред полето за файл. Според ръководството това е само подсказка за браузъра, която лесно се заобикаля. Можете да разчитате единствено на ограниченията, които сървърът налага: в PHP, в уеб сървъра и в самото приложение.

## Ограничения: директивите на php.ini за качване

Тези директиви управляват обработката на качването. Стойностите по подразбиране са от ръководството на PHP и важат, когато никой php.ini не ги презаписва.

| Директива | По подразбиране | Променяема в | 
|---|---|---|
| `file_uploads` | `1` | само системна конфигурация |
| `upload_tmp_dir` | празно (временна директория на системата) | само системна конфигурация |
| `upload_max_filesize` | `2M` | php.ini, пул, конфигурация по директории |
| `post_max_size` | `8M` | php.ini, пул, конфигурация по директории |
| `max_file_uploads` | `20` | php.ini, пул, конфигурация по директории |
| `enable_post_data_reading` | `1` | php.ini, пул, конфигурация по директории |

`post_max_size` трябва да е по-голям от `upload_max_filesize`, а `memory_limit` според ръководството по правило трябва да е по-голям от `post_max_size`. Ако `upload_tmp_dir` не е достъпна за запис, PHP преминава към временната директория на системата. При `open_basedir` временната директория трябва да е разрешена, иначе качването се проваля. Практически стойности и техните последствия описва статията [Разумно ограничаване на post_max_size и upload_max_filesize](https://phpsos.de/bg/blog/post-max-size-upload-limits/).

## Правилно оценяване на кодовете за грешки

Полето `error` показва какво е станало при обработката. Константите са от `UPLOAD_ERR_OK` (0) през `UPLOAD_ERR_INI_SIZE` (1), `UPLOAD_ERR_FORM_SIZE` (2), `UPLOAD_ERR_PARTIAL` (3) и `UPLOAD_ERR_NO_FILE` (4) до `UPLOAD_ERR_NO_TMP_DIR` (6), `UPLOAD_ERR_CANT_WRITE` (7) и `UPLOAD_ERR_EXTENSION` (8).

```php
<?php
declare(strict_types=1);

$datei = $_FILES['lebenslauf'] ?? null;
if ($datei === null) {
    exit('Keine Upload-Daten, eventuell wurde post_max_size überschritten.');
}

$meldung = match ($datei['error']) {
    UPLOAD_ERR_OK => null,
    UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Datei zu groß.',
    UPLOAD_ERR_NO_FILE => 'Keine Datei gewählt.',
    default => 'Upload fehlgeschlagen.',
};

if ($meldung === null && is_uploaded_file($datei['tmp_name'])) {
    $ziel = '/srv/app/uploads/' . bin2hex(random_bytes(16)) . '.pdf';
    move_uploaded_file($datei['tmp_name'], $ziel);
}
```

Примерът показва само механиката. Проверката на типа на файла, съхранението извън webroot и блокирането на изпълнението на скриптове описва наръчникът [Сигурно реализиране на качването на файлове в PHP](https://phpsos.de/bg/ratgeber/datei-upload-sicher/).

## Качване и PHP2Shell: какво означават временните мерки

Уязвимостите PHP2Shell CVE-2026-109618 и CVE-2026-109619 засягат обработката около заявката в интерпретатора на PHP. Затова php.net посочва като временни мерки, наред с другото, `post_max_size = 8M` (без качване) или `32M` (с качване), `upload_max_filesize` под тази стойност, както и `session.upload_progress.enabled=0`. Тези стойности принадлежат в php.ini, FPM пула или конфигурацията на сървъра, след което всички worker процеси трябва да се рестартират.

Специализирани автори предлагат допълнително да се блокират заявките `multipart/form-data` извън истинските пътища за качване на прокси или в WAF. Това е неофициална мярка с риск формите да спрат да работят; преценката описва статията [Филтриране на заявките за качване на периферията](https://phpsos.de/bg/blog/upload-anfragen-am-rand-filtern/). Нито една от тези мерки не замества актуализацията до PHP 8.5.12, 8.4.27, 8.3.36 или 8.2.35.

## Как помага PHP SOS конкретно?

- Установяваме кои от Вашите уебсайтове действително се нуждаят от качване и по кои пътища.
- Задаваме ограниченията и временните мерки за всеки пул, рестартираме всички worker процеси и тестваме формите.
- Проверяваме директориите за качване за изпълними файлове и блокираме изпълнението на скриптове в рамките на [заздравяването на сървъра](https://phpsos.de/bg/leistungen/server-haertung/).
- Внедряваме актуализацията на PHP и документираме състоянието на пачовете за всеки уебсайт.


## Често задавани въпроси

**Къде PHP съхранява първоначално качените файлове?**  
Във временна директория. По подразбиране това е временната директория на системата; с upload_tmp_dir тя може да се промени. Ако файлът не бъде преместен в скрипта, PHP го изтрива в края на заявката.

**Мога ли да вярвам на полето type в $_FILES?**  
Не. MIME типът се съобщава от браузъра, а PHP според ръководството не го проверява. И name, и full_path идват от подателя. Действителния тип на файла определяте от страна на сървъра по съдържанието.

**Защо $_FILES е празен, макар да е изпратен файл?**  
Най-често цялата заявка е била по-голяма от post_max_size. Тогава PHP изчиства напълно $_POST и $_FILES, без код за грешка в масива. Други причини са липсващ enctype във формата или изключена настройка file_uploads.

**Помага ли срещу PHP2Shell, ако забраня качването в приложението?**  
Не. Обработката на данните от заявката се извършва в PHP, преди да се изпълни кодът на приложението. Ефективни са актуализацията на PHP и рестартирането на всички worker процеси; по-тесните ограничения са само временна мярка.

**Каква е разликата между upload_max_filesize и post_max_size?**  
upload_max_filesize ограничава всеки отделен файл, а post_max_size – цялото съдържание на заявката, включително всички файлове и полета. Затова post_max_size трябва да е по-голям от upload_max_filesize.

## Източници

- [Ръководство на PHP: Качване чрез POST](https://www.php.net/manual/de/features.file-upload.post-method.php)
- [Ръководство на PHP: Съобщения за грешки при качване](https://www.php.net/manual/de/features.file-upload.errors.php)
- [Ръководство на PHP: Директиви на php.ini](https://www.php.net/manual/de/ini.core.php)
- [php.net: Съобщения за сигурност](https://www.php.net/)
