Wgrywanie plików na serwer

Wiemy już jak korzystać ze zmiennych, warunków, pętli i funkcji. Zanim przejdziemy do działań na plikach warto by było najpierw dowiedzieć się jak odebrać taki plik od użytkownika i wgrać go na serwer.

Nie przeciągając specjalnie wstępu, aby odebrać plik od użytkownika potrzebujemy odpowiedni formularz.

<form method="POST" enctype="multipart/form-data">
    <input type="file" name="file_field">
    <input type="submit" value="Zapisz" name="submit">
</form>

Aby formularzem można było przesłać plik musi on zawierać atrybut enctype=”multipart/form-data” widoczny w pierwszej linii powyższego przykładu. Do przesłania pliku potrzebujemy także odpowiednie pole fomularza czyli input type=”file”.

Po wybraniu pliku z dysku i przesłaniu formularza, mamy dostęp do informacji o przesyłanych plikach w zmiennej $_FILES. Informacje o plikach są zawarte w tablicy dwuwymiarowej. Napiszmy przykład:

<?php
if(isset($_FILES['file_field'])){
    $file_name = $_FILES['file_field']['name'];
    $file_size = $_FILES['file_field']['size'];
    $file_tmp = $_FILES['file_field']['tmp_name'];
    $file_type = $_FILES['file_field']['type'];
    $file_error = $_FILES['file_field']['error'];
    echo "Nazwa: " . $file_name . "<br>";
    echo "Rozmiar: " . $file_size . " bajtów<br>";
    echo "Nazwa tymczasowa: " . $file_name . "<br>";
    echo "Typ pliku: " . $file_name . "<br>";
    echo "Kod błędu: " . $file_error;
}
?>

W powyższym przykładzie sprawdzamy czy oczekiwany przez nas plik został wysłany i jeśli tak to pobieramy i wyświetlamy dane o nim.

Pierwszy wymiar tablicy to nazwa pliku i tak żeby pobrać informacje o pliku z pola o nazwie „file_field” odnosimy się do $_FILES[’file_field’]. Przez formularz możemy przesłać wiele plików jednocześnie. Dla każdego z plików możemy odczytać dane takie jak: nazwa, rozmiar, typ i nazwa tymczasowa. Nazwa pliku zawiera także jego rozszerzenie , na przykład .jpg dzięki czemu możemy stwierdzić czy jest to zdjęcie czy jakiś inny plik. Na tym etapie możemy pozyskać informacje na przykład do zapisu do bazy danych.

Gdy dajemy użytkownikowi możliwość wgrywania plików na serwer zazwyczaj tworzymy podatność na ataki, dlatego to jeden z elemetów gdzie trzeba się zabezpieczyć. A przed czym się zabezpiczyć? Dwa największe zagrożenia to że ktoś mający pojęcie może wrzucić plik wykonywalny i na przykład skasować nam całą stronę albo narobić różnych dziwnych rzeczy, zyskując pewną kontrolę. Drugie najpopularniejsze zagrożenie to przepełnienie miejsca na dysku. Ze względu na powyższe jeśli mam możliwość, wolę nie dawać użytkownikowi możliwości wgrania plików na serwer.

Posiadając zmienne z powyższego przykładu można stosunkowo łatwo ograniczyć wielkość pliku jaką użytkownik może wgrać, to może np zabezpieczyć nas przed wgraniem przez użytkownika zbyt dużych zdjęć co stosunkowo ograniczy zajęcie miejsca na naszym serwerze.

<?php
    ...
    $file_size = $_FILES['file_field']['size'];
    $max_file_size = 6 * 1024 * 1024; //6 mb
    if($file_size < $max_file_size){
        echo "Plik ma dobry rozmiar, można wgrywać";
    } else {
        echo "Plik jest zbyt duży";
    }
    ...
?>

Rozmiar plików jest podawany w bajtach co oznacza, że jeśli chcemy ograniczyć rozmiar do x MB musimy zastosować odpowiedni przelicznik, tak jak w przykładzie powyżej.

Niestety to ograniczenie zabezpiecza nas tylko przed wgrywaniem zbyt dużych plików, i użytkownik dalej może nam zapchać serwer wieloma małymi plikami. Aby to zrobić lepiej można by zapisywać rozmiary wgranych plików dla adresów IP i blokować po wgraniu okreslonej sumy jednak to też by się dało obejść zmieniając ip (tylko do tego jest potrzebny bardziej zaawansowany „Haker” 🙂 ). Zapewne szerzej o zabezpieczeniach powstanie cała lekcja a może nawet kurs w przyszłości dla bardziej zaawansowanych.

Kolejne podstawowe zabezpieczenie to rozszerzenie pliku. Jeśli chcemy aby użytkownik wgrywał tylko zdjęcia to dajemy mu możliwość wgrywania tylko zdjęć poprzez sprawdzenie rozszerzenia pliku. Najczęściej grafiki mają rozszerzenia .jpg .png .jpeg rzadziej .svg .bmp i inne dlatego przeważnie dajemy możliwość wgrywania pierwszych trzech.

<?php
    ...
    $file_name = $_FILES['file_field']['name'];
    $allowed_extensions = [".jpg",".jpeg",".png"];
    $current_extension = substr ( $file_name , strrpos($file_name,'.' ));
    echo "Twoje rozszerzenie to: " . $current_extension;
    if(in_array($current_extension, $allowed_extensions)){
        echo "Plik ma dobre rozszerzenie, można wgrywać";
    } else {
        echo "Plik jest niewłaściwy.";
    }
    ...
?>

W powyższym przykładzie, aby zabezpieczyć program przed wgraniem różnych dziwnych plików w zmiennej $allowed_extensions definiuję tablicę rozszerzeń jakie dopuszczam. Można tam dodać dowolną ilość rozszerzeń dla różnych innych dokumentów.

W kolejnej linijce pozyskuję rozszerzenie wgrywanego przez użytkownika pliku. Zostały do tego celu wykorzystane funkcje wbudowane PHP, które przycinają nazwę od ostatniego wystąpienia kropki (czyli dokładnie to czego potrzebujemy). Funkcja substr pobiera jako argumenty ciąg znaków wejściowy, miejsce od którego należy przyciąć dany ciąg i miejsce do którego należy zacząć. Podajemy nazwę, ostatnie wystąpienie kropki i trzeciego argumentu nie podajemy (domyślnie zostanie zwrócony ciąg do końca). Do określenia ostatniego wystąpienia kropki w nazwie wykorzystujemy funkcję strrpos, która pobiera jako argumenty ciąg znaków czyli nazwę pliku oraz szukany znak. Dzięki temu rozwiązaniu jeśli mamy plik Nazwa_obrazka.png otrzymamy „.png” a jeśli mamy nazwa_pliku.min.js otrzymamy rozszerzenie „.js”.

Gdy mamy tablicę z określonymi rozszerzeniami oraz rozszerzenie bieżącego pliku wystarczy za pomocą funkcji in_array sprawdzić czy bierzące rozszerzenie jest w tablicy dostępnych. Funkcja pobiera szukany element i tablicę a następnie zwraca false jeśli nie znajdzie elementu.

Teraz gdy już mamy wstępnie zabezpieczony formularz możemy przejść do kwestji zapisu wgrywanego pliku na serwerze.

Najważniejsza z podstawowych kwestii to ścieżka, czyli miejsce gdzie plik ma być wgrany. Można ją okreslić w odniesieniu do pliku w którym jest skrypt lub bezwzględnie podając pełną ścieżkę. W pierwszym przypadku jest łatwiej bo domyślnie plik może się zapisać w katalogu w którym jest skrypt ale i tak polecam drugi sposób na określanie ścieżek ponieważ przy większych projektach ma większy sens.

Jak można zdefiniować ścieżkę.

<?php
    $path = $_SERVER['DOCUMENT_ROOT']. '/opcjonalny_katalog_projektu/folder_plików';
    //na przykład jak mamy projekt pod adresem http://localhost/kurs_php/ i utworzyliśmy katalog images
    $path = $_SERVER['DOCUMENT_ROOT']. '/kurs_php/images';
?>

Zmienna $_SERVER[’DOCUMENT_ROOT’] określa miejsce gdzie są przechowywane nasze pliki ze skryptami. Wystarczy dopisać folder z projektem oraz folder gdzie chcemy umieszczać pliki. Zazwyczaj taką ścieżkę umieszczamy w pliku konfiguracyjnym i dalej nie musimy się zbytnio martwić o ściezki.

No i teraz nadszedł czas na wgranie pliku na serwer.

<?php
    $file_tmp = $_FILES['file_field']['tmp_name'];
    $path = $_SERVER['DOCUMENT_ROOT']. '/kurs_php/images';
    move_uploaded_file($file_tmp, $path . "/" . $file_name);
?>

Do wgrywania plików na serwer służy funkcja move_uploaded_file, która jako argumenty pobiera tymczasową nazwę pliku do wgrania oraz pełną ścieżkę gdzie plik powinien zostać wgrany. Jak łatwo się domyslić, można zmienić nazwę pliku i przechowywać go na serwerze pod zupełnie inną nazwą (np. zaszyfrowaną)

Jeśli uda się wgrać plik to funkcja „move_uploaded_file” zwraca true czyli dodatkowo można wyświetlić komunikat o poprawnym wgraniu pliku jeśli się uda. Warto zauważyć że jeśli wgramy plik o takiej samej nazwie jak istniejący to ten istniejący zostanie nadpisany/podmieniony na nowy.

W ramach podsumowania złóżmy sobie przykładowy kod w całość.

<?php
    if(isset($_FILES['file_field'])){
    $file_name = $_FILES['file_field']['name'];
    $file_size = $_FILES['file_field']['size'];
    $file_tmp = $_FILES['file_field']['tmp_name'];
    $file_type = $_FILES['file_field']['type'];
    $file_error = $_FILES['file_field']['error'];
    $max_file_size = 99 * 1024 * 1024; //6 mb
    if($file_size < $max_file_size){
        $file_name = $_FILES['file_field']['name'];
        $allowed_extensions = [".jpg",".jpeg",".png"];
        $current_extension = substr ( $file_name , strrpos($file_name,'.' ));
        if(in_array($current_extension, $allowed_extensions)){
            $file_tmp = $_FILES['file_field']['tmp_name'];
            $path = $_SERVER['DOCUMENT_ROOT']. '/kurs_php/images';
            $res = move_uploaded_file($file_tmp, $path . "/" . $file_name);
            if($res){
                echo "Plik wgrano na serwer!";
            } else {
                echo "Nie udało się";
            }
        } else {
            echo "Plik jest niewłaściwy.";
        }
    } else {
        echo "Plik jest zbyt duży";
    }
}
?>

To tyle, dalej spróbujemy coś porobić na plikach.