# Kaip PHP apdoroja failų įkėlimus

> Kaip PHP viduje apdoroja failų įkėlimus: multipart/form-data, laikini failai upload_tmp_dir kataloge, masyvas $_FILES, ribinės reikšmės ir klaidų kodai.

Šaltinis: https://phpsos.de/lt/php/datei-uploads-intern/  
Būsena: 2026-10-25  
Teikėjas: PHP SOS, PHP avarinė pagalba ir serverio sauga. Skubi pagalba per „WhatsApp“: https://wa.me/491736494914?text=Sveiki%2C%20PHP%20SOS%2C%20mums%20reikia%20pagalbos%20su%20m%C5%ABs%C5%B3%20PHP%20serveriu.%20%C4%AEmon%C4%97%3A%20%E2%80%A6%20Svetain%C4%97%3A%20%E2%80%A6

**Trumpai:** Kai naršyklė įkelia failą, ji siunčia POST užklausą formatu `multipart/form-data`, kurioje formos laukai ir failų turinys eina vienas po kito kaip atskiros dalys. PHP pats išskaido šią užklausą, kiekvieną failo dalį įrašo į laikiną failą kataloge `upload_tmp_dir`, įrašo duomenis į `$_FILES` ir tik tada paleidžia jūsų skriptą. Skriptas perkelia failą su `move_uploaded_file()`; viską, kas neperkelta, PHP ištrina užklausos pabaigoje.

## Failo kelias nuo naršyklės iki skripto

Sprendimų priėmėjams svarbiausia viena: įkėlimas nėra vien programos veiksmas. Didelė darbo dalis atliekama PHP interpretatoriuje dar prieš įsitraukiant CMS ar parduotuvės kodui. Todėl šios PHP dalies klaidos veikia kiekvieną svetainę, nesvarbu, ar ji turi įkėlimo formą, ar ne.

Eiga trumpai:

1. Išsiunčiama forma su `method="POST"` ir `enctype="multipart/form-data"`.
2. Žiniatinklio serveris priima užklausą ir perduoda ją PHP, priklausomai nuo veikimo režimo per PHP-FPM, mod_php ar kitą sąsają.
3. PHP patikrina ribines reikšmes, tokias kaip `post_max_size`, ir išskaido turinį pagal skiriamąją eilutę, vadinamą boundary.
4. Įprasti laukai patenka į `$_POST`, failų dalys įrašomos į laikinus failus ir aprašomos `$_FILES`.
5. Tik dabar paleidžiamas skriptas, patikrina failą ir perkelia jį į paskirties vietą.
6. Užklausos pabaigoje PHP ištrina visus laikinus failus, kurie nebuvo perkelti.

## multipart/form-data: įkėlimo užklausos sandara

Įkėlimo užklausa yra grynas tekstas su įterptais failo duomenimis. Antraštė `Content-Type` nurodo boundary, o turinyje ši eilutė atskiria atskiras dalis. Kiekviena dalis turi savo antraštes su lauko pavadinimu ir, jei reikia, failo pavadinimu. Stipriai sutrumpintas nekenksmingas pavyzdys:

```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--
```

Visi šio teksto duomenys ateina iš siuntėjo, taip pat failo pavadinimas ir dalies `Content-Type`. PHP juos perima į `$_FILES`, bet neįvertina, ar jie tikėtini.

## Masyvas $_FILES

Laukui, pavadintam `lebenslauf`, `$_FILES['lebenslauf']`, pasak PHP vadovo, turi šiuos raktus:

| Raktas | Turinys | Ar galima pasitikėti? |
|---|---|---|
| `name` | pradinis failo pavadinimas pas siuntėją | ne |
| `full_path` | naršyklės pranešamas kelias, nuo PHP 8.1.0 | ne |
| `type` | MIME tipas pagal naršyklę, PHP netikrinamas | ne |
| `size` | dydis baitais | nustato PHP |
| `tmp_name` | laikino failo kelias serveryje | suteikia PHP |
| `error` | įkėlimo apdorojimo klaidos kodas | nustato PHP |

Laukams su laužtiniais skliaustais pavadinime, pavyzdžiui, `bilder[]`, kiekvienas raktas turi masyvą su vienu įrašu kiekvienam failui. Programa tuomet pereina, pavyzdžiui, per `$_FILES['bilder']['error']` ir tuo pačiu indeksu pasiekia `tmp_name` ir `name`.

Forma papildomai prieš failo lauką gali siųsti paslėptą lauką `MAX_FILE_SIZE`. Pasak vadovo, tai tik užuomina naršyklei, kurią lengva apeiti. Galite pasikliauti tik ribomis, kurias įgyvendina serveris: PHP, žiniatinklio serveryje ir pačioje programoje.

## Ribinės reikšmės: php.ini direktyvos įkėlimams

Šios direktyvos valdo įkėlimų apdorojimą. Numatytosios reikšmės paimtos iš PHP vadovo ir galioja, kai jų nepaneigia php.ini.

| Direktyva | Numatytoji | Keičiama kur |
|---|---|---|
| `file_uploads` | `1` | tik sistemos konfigūracijoje |
| `upload_tmp_dir` | tuščia (sistemos temp) | tik sistemos konfigūracijoje |
| `upload_max_filesize` | `2M` | php.ini, baseine (pool), katalogo konfigūracijoje |
| `post_max_size` | `8M` | php.ini, baseine (pool), katalogo konfigūracijoje |
| `max_file_uploads` | `20` | php.ini, baseine (pool), katalogo konfigūracijoje |
| `enable_post_data_reading` | `1` | php.ini, baseine (pool), katalogo konfigūracijoje |

`post_max_size` turi būti didesnis nei `upload_max_filesize`, o `memory_limit`, pasak vadovo, paprastai turėtų būti didesnis nei `post_max_size`. Jei į `upload_tmp_dir` rašyti negalima, PHP naudoja sistemos temp katalogą. Su `open_basedir` temp katalogas turi būti leistinas, antraip įkėlimai nepavyksta. Praktines reikšmes ir jų pasekmes aprašo straipsnis [Kaip prasmingai apriboti post_max_size ir upload_max_filesize](https://phpsos.de/lt/blog/post-max-size-upload-limits/).

## Teisingas klaidų kodų vertinimas

Laukas `error` nurodo, kas įvyko apdorojant. Konstantos eina nuo `UPLOAD_ERR_OK` (0) per `UPLOAD_ERR_INI_SIZE` (1), `UPLOAD_ERR_FORM_SIZE` (2), `UPLOAD_ERR_PARTIAL` (3) ir `UPLOAD_ERR_NO_FILE` (4) iki `UPLOAD_ERR_NO_TMP_DIR` (6), `UPLOAD_ERR_CANT_WRITE` (7) ir `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);
}
```

Pavyzdys rodo tik mechaniką. Failo tipo patikrą, saugojimą už žiniatinklio šakninio katalogo ribų ir skriptų vykdymo blokavimą aprašo vadovas [Saugus failų įkėlimas PHP](https://phpsos.de/lt/ratgeber/datei-upload-sicher/).

## Įkėlimai ir PHP2Shell: ką reiškia pereinamosios priemonės

PHP2Shell spragos CVE-2026-109618 ir CVE-2026-109619 liečia užklausos apdorojimą PHP interpretatoriuje. Todėl php.net kaip pereinamąsias priemones nurodo, be kita ko, `post_max_size = 8M` (be įkėlimų) arba `32M` (su įkėlimais), mažesnį `upload_max_filesize` ir `session.upload_progress.enabled=0`. Šios reikšmės turi būti php.ini, FPM baseine arba serverio konfigūracijoje, o po to visi darbiniai procesai (worker) turi būti paleisti iš naujo.

Specialistai papildomai siūlo `multipart/form-data` užklausas už tikrųjų įkėlimo kelių blokuoti tarpiniame serveryje (proxy) arba WAF. Tai neoficiali priemonė, kuri gali sugadinti formas; svarstymus aprašo straipsnis [Įkėlimo užklausų filtravimas ties riba](https://phpsos.de/lt/blog/upload-anfragen-am-rand-filtern/). Nė viena iš šių priemonių neatstoja atnaujinimo iki PHP 8.5.12, 8.4.27, 8.3.36 ar 8.2.35.

## Kaip PHP SOS padeda konkrečiai?

- Nustatome, kurioms jūsų svetainėms įkėlimai iš tikrųjų reikalingi ir kokiais keliais.
- Nustatome ribines reikšmes ir pereinamąsias priemones kiekvienam baseinui (pool), paleidžiame iš naujo visus darbinius procesus (worker) ir išbandome formas.
- Tikriname įkėlimų katalogus dėl vykdomų failų ir blokuojame skriptų vykdymą pagal [serverio sutvirtinimo](https://phpsos.de/lt/leistungen/server-haertung/) paslaugą.
- Įdiegiame PHP atnaujinimą ir dokumentuojame pataisų būseną kiekvienai svetainei.


## Dažni klausimai

**Kur PHP pirmiausia saugo įkeltus failus?**  
Laikinajame kataloge. Pagal numatymą tai sistemos laikinasis katalogas; jį galima pakeisti per upload_tmp_dir. Jei skripte failas neperkeliamas, PHP jį ištrina užklausos pabaigoje.

**Ar galiu pasitikėti laukeliu type masyve $_FILES?**  
Ne. MIME tipą praneša naršyklė, o PHP, pasak vadovo, jo netikrina. Taip pat name ir full_path ateina iš siuntėjo. Tikrąjį failo tipą nustatykite serverio pusėje pagal turinį.

**Kodėl $_FILES tuščias, nors failas buvo išsiųstas?**  
Dažniausiai visa užklausa buvo didesnė nei post_max_size. Tuomet PHP visiškai ištuština $_POST ir $_FILES, o masyve klaidos kodo nėra. Kitos priežastys: formoje trūksta enctype arba išjungtas file_uploads.

**Ar nuo PHP2Shell padeda uždrausti įkėlimus programoje?**  
Ne. Užklausos duomenys apdorojami PHP viduje, dar prieš pradedant vykdyti programos kodą. Veiksmingas yra PHP atnaujinimas ir visų darbinių procesų (worker) paleidimas iš naujo; griežtesnės ribinės reikšmės yra tik pereinamoji priemonė.

**Kuo skiriasi upload_max_filesize ir post_max_size?**  
upload_max_filesize riboja kiekvieną atskirą failą, o post_max_size visą užklausos turinį, įskaitant visus failus ir laukus. Todėl post_max_size turi būti didesnis nei upload_max_filesize.

## Šaltiniai

- [PHP vadovas: įkėlimai per POST](https://www.php.net/manual/de/features.file-upload.post-method.php)
- [PHP vadovas: klaidų pranešimai įkeliant failus](https://www.php.net/manual/de/features.file-upload.errors.php)
- [PHP vadovas: php.ini direktyvos](https://www.php.net/manual/de/ini.core.php)
- [php.net: saugumo pranešimai](https://www.php.net/)
