sie 30 2008

System downloadu z ukrywaniem fizycznych plików

Kategoria: PHPPiotr Surma @ 23:43

Napiszemy prosty system downloadu plików na stronie, który umożliwi nam podjęcie jakiejś akcji po uzyskaniu żądania pobierania (np. zliczenie pobrań danego pliku) oraz uniemożliwi bezpośredni dostęp do fizycznego pliku, nawet jeśli znany jest link do niego (celem chociażby zablokowania ominięcia wyżej wymienionych statystyk). Tego typu system przydatny jest także w przypadku dostępu do płatnego downloadu. Zapewni wtedy pobieranie tylko po uiszczeniu odpowiedniej opłaty.

Jeśli jednak nie chcesz blokować dostępu do pliku z bezpośredniego linku, omiń poniższy akapit.

Blokowanie dostępu do pliku

Na początku zablokujemy dostęp do pliku z poziomu przeglądarki. Najlepiej w takim przypadku utworzyć oddzielny folder, w którym będziemy przechowywali pliki udostępniane użytkownikom strony. Nazwijmy go downloads. Aby zablokować dostęp do tego katalogu z zewnątrz, tworzymy w nim plik o nazwie .htaccess (bez nazwy i z rozszerzeniem htaccess). System Windows niestety nie pozwoli na zapisanie pliku bez nazwy, dlatego lepiej będzie posłużyć się zewnętrznym edytorem, który nie będzie miał z tym problemu (np. EditPad Lite).

W treści tego pliku, wpisujemy:

  1. deny from all

Należy pamiętać, aby pozostawić pustą ostatnią linię w pliku .htaccess

Jeśli nie chcesz blokować dostępu do wszystkich plików w danym katalogu, a jedynie do kilku wybranych, należy użyć takiej składni:

  1. <Files mojplik.exe>
  2.    deny from all
  3. </Files>

Teraz próba dostępu do pliku bezpośrednio znając jego adres (np. www.example.com/downloads/mojplik.exe ) zakończy się wyświetleniem błędu 403 Forbidden.

Mechanizm pobierania (download)

Skrypt wysyłający plik do użytkownika jest wbrew pozorom krótki i nieskomplikowany. Najpierw sczytujemy do zmiennej zawartość pliku (PHP ma dostęp do blokowanego pliku):

  1. $zawartosc=file_get_contents('downloads/mojplik.exe');

Następnie musimy wysłać do przeglądarki kilka nagłówków, informując ją tym samym z czym za chwilę będzie miała do czynienia. Innymi słowy musimy ją poinformować co jej wyślemy i jakie to będzie duże.

Nagłówek typu MIME pliku:

  1. header('Content-Type: application/octet-stream');

Informuje on, jaki typ pliku za chwilę zostanie przesłany. Dla plików wykonywalnych exe, typ MIME będzie wyglądał tak jak powyżej (application/octet-stream). Różne typy plików mają różną wartość mimetype. Oto kilka najpopularniejszych:

  • Archiwum RAR – application/x-rar-compressed
  • Archiwum ZIP – application/zip
  • Plik muzyczny MP3 – audio/mpeg3
  • Plik tekstowy TXT – text/plain
  • Film AVI – video/x-msvideo
  • Film MPEG – video/mpeg
  • Dokument PDF – application/pdf
  • Dokument DOC – application/msword

Jeśli nie znasz typu MIME danego pliku, możesz ustawić typ application/octet-stream, który jest uważany również za domyślny.

Kolejnym nagłówkiem, jest nagłówek długości (wielkości pliku). Informujemy przeglądarkę jaki rozmiar wyrażony w bajtach ma plik. Jeśli tego nie zrobimy, wszystko będzie działać prawidłowo, ale przeglądarka podczas pobierania nie znając rozmiaru pliku nie wyświetli paska postępu (tylko animację pobierania) oraz nie poda użytkownikowi przewidywanego czasu ukończenia pobierania. Wielkość pliku uzyskamy i prześlemy przeglądarce w następujący sposób:

  1. header('Content-Length: '.strlen($zawartosc));

Rozmiar pliku możemy także pobrać funkcją filesize().

Ostatni nagłówek to przesłanie do przeglądarki żądania wyświetlenia okienka pobierania:

  1. header('Content-Disposition: attachment; filename=mojplik.exe');

Zauważ, że w tym wywołaniu możemy manipulować nazwą pliku, jaką dostanie użytkownik. Dzięki temu wysyłany plik nie musi mieć konkretnie takiej nazwy, jaką ma na serwerze, np:

  1. header('Content-Disposition: attachment; filename=twojplik.exe');

Na koniec najnormalniej w świecie “wyświetlamy” wartość zmiennej $zawartosc:

  1. echo $zawartosc;
  2. exit;

exit; na końcu umieściłem stricte zapobiegawczo :)

Podsumowując.

Cały kod

  1. <?php
  2.    $zawartosc=file_get_contents('downloads/mojplik.exe');
  3.  
  4.    header('Content-Type: application/octet-stream');
  5.    header('Content-Length: '.strlen($zawartosc));
  6.    header('Content-Disposition: attachment; filename=mojplik.exe');
  7.  
  8.    echo $zawartosc;
  9.    exit;
  10. ?>

Kod zapisujemy jako download.php. Teraz na naszej stronie w dziale download nie umieszczamy bezpośredniego linku do pliku mojplik.exe, tylko link do download.php.

Oczywiście jeśli naszym celem było zliczanie pobrań, przed kodem wysyłającym plik do przeglądarki powinniśmy umieścić kod licznika.

Przykładowy licznik zapisujący dane w pliku tekstowym może być taki:

  1. <?php
  2.    $licznik=file_get_contents('licznik.txt');
  3.    if(!is_numeric($licznik)) $licznik=0;
  4.    file_put_contents('licznik.txt',++$licznik);
  5.  
  6.    $zawartosc=file_get_contents('downloads/mojplik.exe');
  7.  
  8.    header('Content-Type: application/octet-stream');
  9.    header('Content-Length: '.strlen($zawartosc));
  10.    header('Content-Disposition: attachment; filename=mojplik.exe');
  11.  
  12.    echo $zawartosc;
  13.    exit;
  14. ?>

Nie należy zapomnieć, aby utworzyć plik licznik.txt oraz nadać mu prawa do zapisu.

Uwaga! Jako że skrypt ładuje całą zawartość pliku do pamięci, rozmiar pliku nie może przekraczać wartości zapisanej w dyrektywie memory_limit w pliku konfiguracyjnym php.ini

2 Odpowiedzi do “System downloadu z ukrywaniem fizycznych plików”

  1. mateyko says:

    Świetny, przejrzysty wpis. Dzięki!

  2. macer(forum ubuntu) says:

    Super kurs!
    Nie wiedziałem że wogóle coś takiego można zrobić

Pozostaw Odpowiedź