W budowie
Materiał edukacyjny · Informatyka System plików, dzienniki zdarzeń, format CSV oraz bezpieczny upload plików graficznych

CZĘŚĆ 19

System plików i przesyłanie plików na serwer (upload)

PPrezentacja dla nauczyciela

W wielu zadaniach egzaminacyjnych INF.03 dane nie trafiają wyłącznie do bazy danych, ale są również zapisywane w plikach tekstowych (np. dzienniki logów, raporty CSV) lub przesyłane z formularza przez użytkownika (np. zdjęcia produktów, załączniki). W tej części opanujesz operacje na systemie plików oraz bezpieczny mechanizm przesyłania plików za pomocą tablicy $_FILES i funkcji move_uploaded_file().

Odczyt i zapis plików

Do pracy z plikami służą file_get_contents() (odczyt całości) i file_put_contents() (zapis). Są proste, ale wczytują cały plik do pamięci.

Przy dużych plikach lepsze są fopen(), fgets() i fclose(), które czytają linia po linii. Tryb otwarcia określa, czy plik ma być czytany, dopisywany czy nadpisany.

Każda operacja na plikach wymaga sprawdzenia wyniku. Brak praw zapisu albo nieistniejący katalog to najczęstsze przyczyny błędów, a nie błędy w samym kodzie.

$sciezka = __DIR__ . '/dane/log.txt';

file_put_contents($sciezka, date('Y-m-d H:i') . " start\n", FILE_APPEND);

if (is_file($sciezka)) {
    $linie = file($sciezka, FILE_IGNORE_NEW_LINES);
    foreach ($linie as $linia) {
        echo '<p>' . htmlspecialchars($linia) . '</p>';
    }
}
1. Co robi file_get_contents()?
2. Do czego służy FILE_APPEND?
3. Co sprawdzić przed odczytem?

Przesyłanie pliku na serwer

Formularz z plikiem wymaga method="post" i atrybutu enctype="multipart/form-data". Bez enctype przeglądarka wyśle tylko nazwę pliku.

Dane trafiają do tablicy $_FILES z kluczami name, tmp_name, size i error. Plik trzeba przenieść z katalogu tymczasowego funkcją move_uploaded_file().

Zawsze sprawdzamy kod błędu, rozmiar i rozszerzenie. Nazwę pliku generujemy sami, żeby uniknąć nadpisania i dziwnych znaków w adresie.

if (($_FILES['plik']['error'] ?? 1) === UPLOAD_ERR_OK) {
    $nazwa = basename($_FILES['plik']['name']);
    $koncowka = strtolower(pathinfo($nazwa, PATHINFO_EXTENSION));

    if (in_array($koncowka, ['jpg', 'png'], true) && $_FILES['plik']['size'] < 2000000) {
        $nowa = uniqid('img_', true) . '.' . $koncowka;
        move_uploaded_file($_FILES['plik']['tmp_name'], __DIR__ . '/upload/' . $nowa);
        echo 'Zapisano: ' . htmlspecialchars($nowa);
    } else {
        echo 'Nieprawidlowy plik';
    }
}
4. Czego wymaga formularz z plikiem?
5. Gdzie jest plik przed przeniesieniem?
6. Co robi move_uploaded_file()?

Bezpieczeństwo plików i katalogów

Największe ryzyko to wykonanie przesłanego pliku. Jeśli użytkownik wgra plik .php do katalogu publicznego, serwer może go uruchomić. Dlatego sprawdzamy rozszerzenie i zapisujemy pliki poza katalogiem z kodem.

Nazwę pliku budujemy sami i nie ufamy $_FILES['name']. Znaki ../ w nazwie mogą wskazać inny katalog — to tak zwana path traversal.

Dodatkowo ograniczamy rozmiar i typ MIME, a katalogowi uploadu odbieramy prawo wykonywania skryptów. To kilka linii konfiguracji, które zamykają najczęstszą lukę.

$dozwolone = ['jpg' => 'image/jpeg', 'png' => 'image/png'];
$rozszerzenie = strtolower(pathinfo($_FILES['plik']['name'] ?? '', PATHINFO_EXTENSION));
$typ = $_FILES['plik']['type'] ?? '';

if (!isset($dozwolone[$rozszerzenie]) || $dozwolone[$rozszerzenie] !== $typ) {
    exit('Niedozwolony typ pliku');
}

$nazwa = bin2hex(random_bytes(8)) . '.' . $rozszerzenie;
move_uploaded_file($_FILES['plik']['tmp_name'], __DIR__ . '/upload/' . $nazwa);
7. Dlaczego sprawdzamy rozszerzenie?
8. Czym jest path traversal?
9. Skąd brać nazwę pliku?

Ćwiczenia