Jak pobrać plik, licząc liczbę ściągnięć?

Adam Boduch

Kod źródłowy tego skryptu pochodzi z projektu Coyote i został trochę przeze mnie zmodyfikowany.

Najprostszy sposób

Najprostszym sposobem jest po prostu przekierowanie skryptu na stronę z plikiem. Np.

header('Location: http://serwer.com/plik.zip');

Wczesniej oczywiście w bazie danych notując ściągnięcie. Ja jednak przedstawię nieco bardziej zaawansowany sposób pozwalający na ukrycie realnej nazwy pliku.

Nieco trudniejszy sposób

Wszystko opiera się na wysłaniu odpowiedniego nagłówka przy pomocy funkcji header().

header('Pragma: no-cache');
header('Content-Type: application/x-zip-compressed; name="plik.zip"');
header('Content-Disposition: attachment; filename="plik.zip"');
header('Content-Length: 1024');

Taki kod spowoduje otwarcie w przeglądarce okienka "Zapisz jako..". Przekazane tu nagłówki mówią o tym, że ściągany plik jest typu ZIP (application/x-zip-compressed), jego rozmiar to 1 kB, a nazwa to plik.zip. Teraz już wystarczy wysłać do przeglądarki zawartość pliku - np.

readFile('download/plik.zip');

Od tego momentu serwer wyśle zawartość pliku download/plik.zip do klienta (przeglądarki), a przeglądarka rozpocznie pobieranie go, a w późniejszej fazie zapisywanie go. Nie polecam jednak stosowania funkcji readFile().

Po pierwsze, zwracając cały plik, to cały plik jest wczytywany do RAMU (spora część pamięci jest przez to używana). Po drugi, można za jej pomocą jedynie wyświetlić pliki znajdujące się w lokalnym systemie plików - nie można za jej pomocą ściągnąć pliku z innego serwera. Lepiej jest do tego użyć funkcji fopen(), fgets().

Oto cały skrypt download.php z projektu Coyote:

<?
/****************************************************************************
*                           download.php
*                      -------------------------
*       rozpoczety          : 11.03.2003 r.
*       wersja              : $Revision: 1.5 $
*       zmiana              : $Date: 2003/08/12 20:45:52 $
*
*****************************************************************************

  Skrypt sluzy do liczania ilosci sciagniec pliku, a po uaktualnieniu w bazie
  danych rozpoczyna sie proces przekierowania na wybrany plik, do sciagniecia

****************************************************************************/

  include 'functions.php';

  // sprawdzaj, czy wraz z URL dostarczony jest paramtr ID
  if (empty($_GET['id']))
  {
      Error('Złe wywołanie programu!', '', 0, NORMAL_ERROR);
  }

  $sql = 'SELECT file_name, file_type, file_url, file_size FROM ' . FILES . ' WHERE file_id=' . $_GET['id'];
  if ($db->sql_query($sql))
  {
      /* jezeli zapytanie sie powiodlo, odczytaj zawartosc okreslonego rekordu, w tym jego
         URL, ktory bedzie potrzebny */
      $row = $db->sql_fetch();

     // ostatnie zapytanie ma za zadanie zwiekszyc licznik, sciagniec pliku o 1
      $sql = 'UPDATE ' . FILES . ' SET file_downloads=(file_downloads+1) WHERE file_id=' . $_GET['id'];
      $db->sql_query($sql);

      $extension = Array(
          'zip' => 'application/x-zip-compressed',
          'exe' => 'application/exe',
          'pdf' => 'application/pdf',
          'pas' => 'text/plain',
          'html' => 'text/html');

      // odczytanie rozszerzenia pliku
      preg_match("/\.(.*)$/", $row['file_name'], $matches);

      $file = fopen($row['file_url'], 'r');

     /* ponizsze instrukcje powoduja wyslanie pliku do przegladarki (innymi slowy - sciagniecie go) */
      header('Pragma: no-cache');
      header('Content-type: ' . $extension[$matches[1]] . '; name="' . $row['file_name'] . '"');
      header('Content-Disposition: attachment; filename="' . $row['file_name'] . '"');
      header('Content-Length: ' . ($row['file_size'] * 1024));

      while (!feof($file))
      {
          echo fread($file, 1024);
      }

      fclose($file);


      exit;

  }

// $Id: download.php,v 1.5 2003/08/12 20:45:52 Adam Exp $
?>

Najpierw z bazy danych następuje pobranie informacji o pliku (nazwa, rozmiar, ścieżka URL). Następnie następuje zwiększenie wartości kolumny "counter", która odpowiada za przechowywanie ilości ściągnięć pliku. Później mozna już otworzyć URL (korzystając z funkcji fopen()). Teraz można już odczytywać plik kawałek po kawału (po 1 kB) i wysyłać do klienta.


Notka

Jak zauważyłeś podczas wysyłania nagłówka należy określic typ [MIME] wysyłanych danych. Ja zadeklarowałem w tym skrypcie tablicę $extension, która zwiera rozszerzenia plików oraz ich typ [MIME]. Istnieje w PHP funkcja [mime_content_type()] która podaje typ MIME pliku; wystarczy podać w funkcji ścieżkę pliku jako parametr. Jednak, żeby skorzystać z tej funkcji należy skompilować PHP z parametrem: --with-mime-magic. Dodatkowo funkcja [mime_content_type()] działa tylko na lokalnych plikach, tzn. dostępnych w obrębie jednego serwera. Jeżeli chcesz pobrać typ [MIME] pliku, który znajduje się na innym serwerze, niestety [mime_content_type()] tutaj się nie przyda.

[MIME]: http://pl.wikipedia.org/wiki/MIME)
[mime_content_type()]: https://www.php.net/manual/en/function.mime-content-type.php

2 komentarzy

Może i prościej ale ta funkcja daje dostęp do plików które nie są dostępne przez www.

A nie prościej zrobić PHP+MySQL
Podawać userowi link do <font class="adtext" onmouseover="fo_emituj_reklame(8)" onmouseout="fo_ukryj_reklame()">pliku</span> np.
http://mojastrona.phpw/download.php?fid=1111
I wtedy:

<?php $sql = mysql_fetch_array(mysql_query('select * from `download` where `id`="'. $_GET['fid'] .'"')); $pobran = $sql[pobran]+1; mysql_query('update `download` set `pobran`="'. $pobran .'" where `id`="'. $_GET['fid'] .'"'); header("Location: ". $sql[plik]); ?>