PHP: Прячем файлы от прямого скачивания

Иногда при разработке сайтов можно столкнуться с задачей, когда нам нужно дать возможность скачать тот или иной файл со своего сервера, однако мы не хотим чтобы пользователи знали место его хранения, а порой вообще необходимо защититься от возможности скачать данный файл по прямой ссылке.

Я рассмотрю данную задачу в рамках второго случая, так как он ничем не отличается от первого, за исключением места хранения наших файлов.
Допустим наш сайт хранится на виртуальном хостинге в папке «/usr/myname/www/mysite.ru/» таким образом когда пользователи заходят на Ваш сайт они попадают именно в эту папку. При этом доступа «Назад» они не имеют, поэтому вполне логично для хранения файлов использовать путь, например, «/usr/myname/www/files/». Попасть в эту папку из вне (конечно если Вас не взломали) будет невозможно, поэтому можно смело скидывать туда все файлы, которые мы хотим защитить. Для большей наглядности определим, что в папке «/usr/myname/www/files/» у нас лежат файлы «1.exe», «2.exe» и т.д..
Так же определимся, что за скачивание файлов у нас будет отвечать скрипт «/usr/myname/www/mysite.ru/download.php» к которому можно обратиться «http://mysite.ru/download.php». Рассмотрим его содержимое:

<?php  
  if(empty($_GET['file'])) die('Неверный запрос'); //проверим есть ли параметр с именем файла
  $file = "/usr/myname/www/files/".$_GET['file']; //полный путь к нашему файлу 
  if(!file_exists($file)) die('Файл не найден'); //проверим существует ли файл
  //Заголовки ответа на запрос, что отвечаем мы потоком байт
  header ("Content-Type: application/octet-stream");
  header ('Content-Description: File Transfer');
  header ("Accept-Ranges: bytes");
  header ('Content-Transfer-Encoding: binary');
  header ("Content-Length: ".filesize($file));
  header ("Content-Disposition: attachment; filename=".basename($file));  
  //Передаем наш файл в ответ на запрос
  readfile($file);
?>

Теперь если мы перейдем по ссылке «http://mysite.ru/download.php?file=1.exe» — мы начнем скачивать файл «1.exe» и т.д.. Собственно и все. Все достаточно просто.

Важно: данный пример несет чисто информативный характер и по больше части не явялется реализацией задачи спрятать файл. Данный пример имеет уязвимость! Если вы не догадались, то подумайте какую, если лень думать, то читайте дальше. У данного подхода не хватает проверки для переменной $_GET[‘file’]. Если передать в качестве параметра строку «../mysite.ru/[путь_к_файлу]» — это приведет к скачиванию любого файла с Вашего сайта, что может повлечь, в лучшем случае, утечку данных. Лучше вообще отказаться от передачи имени файла в качестве параметра для данной функции. Хорошим решением будет исользование переменной сессии, которая будет инициализированна заранее, или использование БД для хранения путей к файлам, и передачи в качестве параметра айди необходимой записи (так же не забываем про проверки разрешения доступа пользователю на скачивание запрашиваемого файла).