Скачивание файлов по временным ссылкам

02.10.2010

Теги: HTTPMySQLPHPWeb-разработкаФайл

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

Для хранения информации о файлах и временных ссылках будем использовать БД. Таблица files хранит информацию о файлах:

CREATE TABLE `files` (
  `id` INT(10) PRIMARY KEY,
  `title` VARCHAR(255) NOT NULL DEFAULT '',
  `description` TEXT NOT NULL DEFAULT '',
  `filename` VARCHAR(64) NOT NULL DEFAULT '',
  `mimetype` VARCHAR(8) NOT NULL DEFAULT ''
) ENGINE=INNODB DEFAULT CHARSET=cp1251;

Здесь

  • id — уникальный ID файла
  • title — название файла, например, «Текстовой редактор NotePad++»
  • description — описание файла, например, «Бесплатный редактор текстовых файлов (замена стандартного Блокнота) с поддержкой синтаксиса большого количества языков программирования, ориентирован для работы в операционной системе MS Windows»
  • filename — имя файла для скачивания, например, NotePadPP.zip
  • mimetype — MIME-тип файла

Таблица downloads хранит информацию о временных ссылках:

CREATE TABLE `downloads` (
  `file_id` INT(10) NOT NULL DEFAULT 0,
  `uniq_id` VARCHAR(32) NOT NULL DEFAULT '',
  `puttime` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=INNODB DEFAULT CHARSET=cp1251;

Здесь

  • file_id — уникальный ID файла
  • uniq_id — временная ссылка
  • puttime — время создания ссылки

Файлы для скачивания расположены в директории DOCUMENT_ROOT/download/files/. Эта директория должна быть защищена с помощью .htaccess:

Order Allow,Deny
Deny from All

Скрипт, который будет выполнять всю работу — выводить список файлов, генерить временные ссылки, и отдавать файлы на скачивание — DOCUMENT_ROOT/download/index.php

<?php
// Соединяемся с сервером БД
mysql_connect ( 'localhost', 'root', '' );
mysql_query( 'SET NAMES cp1251' );
mysql_select_db ( 'downloads' );

// удаляем устаревшие записи в таблице БД downloads
$query = "DELETE FROM `downloads` WHERE `puttime` < (NOW() - INTERVAL 12 HOUR)";
mysql_query( $query );

$actions = array( 'fileslist', 'getlink', 'download' );

$action = 'fileslist';
if( isset( $_GET['action'] ) and in_array( $_GET['action'], $actions ) ) $action = $_GET['action'];

switch( $action ) {
  case 'fileslist':      // список файлов для скачивания
    fileslist(); break;
  case 'getlink':        // создаем временную ссылку
    getlink(); break;
  case 'download':       // отдаем файл на скачивание
    download();  break;
}

function fileslist() {
  echo '<h3>Файлы для скачивания</h3>'."\n";
  $query = "SELECT `id`, `title`, `description`, `mimetype` FROM `files` WHERE 1 ORDER BY `title`";
  $res = mysql_query( $query );

  echo '<table border="1">'."\n";
  echo '<tr><th>№</th><th>Наименование</th><th>Описание</th><th>Тип</th><th>Скачать</th></tr>'."\n";
  $i = 1;
  while( $file = mysql_fetch_array( $res ) ) {
    echo '<tr>';
    echo '<td>'.$i.'</td>';
    echo '<td>'.$file['title'].'</td>';
    echo '<td>'.$file['description'].'</td>';
    echo '<td>'.$file['mimetype'].'</td>';
    echo '<td><a href="'.$_SERVER['PHP_SELF'].'?action=getlink&id='.$file['id'].'">Скачать</a></td>';
    echo '</tr>'."\n";
    $i++;
  }
  echo '</table>'."\n";
}

function getlink() {
  // если не передан уникальный ID файла - значит пользователь попал сюда по ошибке
  if( !isset( $_GET['id'] ) ) {
    header( 'Location: '.$_SERVER['PHP_SELF'].'?action=fileslist' );
    die();
  }
  $id = (int)$_GET['id'];

  // прежде чем генерить временную ссылку, проверяем, что есть такая запись в таблице БД
  $query = "SELECT 1 FROM `files` WHERE `id`=".$id;
  $res = mysql_query( $query );
  if( mysql_num_rows( $res ) == 0 ) {
    header ( 'HTTP/1.1 404 Not Found' );
    die(); 
  }
 
  $uniq_id = md5( uniqid(rand(), 1) );
  $query = "INSERT INTO `downloads` (`file_id`, `uniq_id`, `puttime`)
            VALUES (".$id.", '".$uniq_id."', NOW())";
  mysql_query( $query );
 
  $link = $_SERVER['PHP_SELF'].'?action=download&id='.$id.'&code='.$uniq_id;
  echo '<p>Для загрузки файла перейдите по <a href="'.$link.'">этой ссылке</a>. ';
  echo 'Ссылка действительна в течение 12 часов.</p>'."\n";
}

function download() {
  // если не передан уникальный ID файла - значит пользователь попал сюда по ошибке
  if( !isset( $_GET['id'] ) ) {
    header( 'Location: '.$_SERVER['PHP_SELF'].'?action=fileslist' );
    die();
  }
  $id = (int)$_GET['id'];
 
  if( !isset( $_GET['code'] ) )  {
    header( 'Location: '.$_SERVER['PHP_SELF'].'?action=fileslist' );
    die();
  }
 
  if( !preg_match( '#[a-f0-9]{32}#', $_GET['code'] ) )  {
    header ( 'HTTP/1.1 404 Not Found' );
    die();
  }
 
  $query = "SELECT 1 FROM `downloads` WHERE `file_id`=".$id."
            AND `uniq_id`='".$_GET['code']."' AND `puttime` > (NOW() - INTERVAL 12 HOUR)";
  $res = mysql_query( $query );
  if( mysql_num_rows( $res ) == 0 ) {
    header ( 'HTTP/1.1 404 Not Found' );
    die(); 
  }
 
  $query = "SELECT `filename`, `mimetype` FROM `files` WHERE `id`=".$id;
  $res = mysql_query( $query );
  if( mysql_num_rows( $res ) == 0 ) {
    header ( 'HTTP/1.1 404 Not Found' );
    die(); 
  }
  list( $filename, $mimetype ) = mysql_fetch_row( $res );
 
  // если файла нет
  if( !file_exists( './files/'.$filename ) ) {
    header ( 'HTTP/1.1 404 Not Found' );
    die();
  }
 
  // получаем размер файла
  $fsize = filesize( './files/'.$filename );
  // дата модификации файла для кеширования
  $ftime = date( 'D, d M Y H:i:s T', filemtime( './files/'.$filename ) );
  // смещение от начала файла
  $range = 0;
 
  // пробуем открыть
  $handle = @fopen( './files/'.$filename, 'rb' );

  // если не удалось
  if( !$handle ){
    header ( 'HTTP/1.1 404 Not Found' );
    die();
  }
 
  // если запрашивающий агент поддерживает докачку
  if( $_SERVER['HTTP_RANGE'] ) {
    $range = $_SERVER['HTTP_RANGE'];
    $range = str_replace( 'bytes=', '', $range );
    $range = str_replace( '-', '', $range );
    // смещаемся по файлу на нужное смещение
    if ( $range ) fseek( $handle, $range );
  }
 
  // если есть смещение
  if( $range ) {
    header( 'HTTP/1.1 206 Partial Content' );
  } else {
    header( 'HTTP/1.1 200 OK' );
  }
 
  header( 'Content-Disposition: attachment; filename="'.$filename.'"' );
  header( 'Last-Modified: '.$ftime );
  header( 'Content-Length: '.($fsize-$range) );
  header( 'Accept-Ranges: bytes' );
  header( 'Content-Range: bytes '.$range.'-'.($fsize - 1).'/'.$fsize );

  switch( $mimetype ) {
    case 'pdf' : $ctype = 'application/pdf'; break;
    case 'zip' : $ctype = 'application/zip'; break;
    case 'doc' : $ctype = 'application/msword'; break;
    case 'xls' : $ctype = 'application/vnd.ms-excel'; break;
    case 'gif' : $ctype = 'image/gif'; break;
    case 'png' : $ctype = 'image/png'; break;
    case 'jpeg':
    case 'jpg' : $ctype = 'image/jpg'; break;
    case 'mp3' : $ctype = 'audio/mpeg'; break;
    case 'wav' : $ctype = 'audio/x-wav'; break;
    case 'mpeg':
    case 'mpg' :
    case 'mpe' : $ctype = 'video/mpeg'; break;
    case 'mov' : $ctype = 'video/quicktime'; break;
    case 'avi' : $ctype = 'video/x-msvideo'; break;
    default    : $ctype = 'application/octet-stream';
  }
 
  header( 'Content-Type: '.$ctype );
  readfile( './files/'.$filename );
  fclose( $handle );
}
?>

Поиск: HTTP • MySQL • PHP • Web-разработка • Файл

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.