Скачивание файлов по временным ссылкам
Наверное каждому приходилось сталкиваться с временными ссылками при скачивании фильмов, музыки, программ и т.п. Зачем это делается? Да чтобы другие сайты не размещали ссылки на файлы, которые расположены на нашем сайте. Давайте посмотрим, как написать скрипт, который будет генерить временные ссылки.
В качестве хранения информации о файлах и временных ссылках, будем использовать БД. Таблица 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 хранит информацию о временных ссылках:
`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:
Deny from All
Скрипт, который будет выполнять всю работу - выводить список файлов, генерить временные ссылки, и отдавать файлы на скачивание - DOCUMENT_ROOT/download/index.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 );
}
?>
Артём Курапов:
Я нечто похожее использую у себя в библиотеке aleria.net, но не основную часть. readfile ведь умрёт если файл очень большой а скачивается он медленно.
2 Июнь 2009, 19:13admin:
Артём Курапов, Вы правы - умрет по timeout. Наверное, есть смысл использовать
если хостер позволит. Правда, я предусмотрел возможность докачки файла. Вообще, мне ни разу не приходилось реализовывать подобный алгоритм на практике: эта заметка - попытка осознать проблему и пути ее решения. Буду благодарен за любые намеки для полного решения этой задачи.
2 Июнь 2009, 22:26iphone:
Спасибо! как раз я работаю над проектом в которм есть такая задача.
15 Июнь 2009, 20:16ygen:
Давно хотел прикрутить подобнрую фишку для скачивания по временным ссылкам… буду разбираться,спасибо.
22 Июнь 2009, 16:16Денис:
Подскажите не могу настроить скрипт выдает ошибку Warning: mysql_num_rows(): supplied argument is not a valid MySQL result resource in /home/hitchar/public_html/okmp3.ru/download/index.php on line 98
15 Июль 2009, 11:43Я фаил залил в папку files занес его через phpadmin в БД
все нормально до моментка где нцжно скачать фаил т..е переходишь скачать потом в течение 12 часов это ссылка действительно и когда на нее жмешь она выдает эту ошибку
admin:
Подскажите не могу настроить скрипт выдает ошибку Warning: mysql_num_rows(): supplied argument is not a valid MySQL result resource in /home/hitchar/public_html/okmp3.ru/download/index.php on line 98
16 Июль 2009, 9:19Перевожу: переданный функции mysql_num_rows() аргумент не является результатом запроса к базе данных. Это моя вина: я написал
NOW() - INTERVAL 12 HOURS
а надо
NOW() - INTERVAL 12 HOUR
Исправил.
alexey:
заинтересовали решил попробовать,наткнулся на ->
Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in W:\home\localhost\www\DOCUMENT_ROOT\download\index.php on line 35
в коде:
function fileslist() {
…
…
->>> while( $file = mysql_fetch_array( $res ) ) {
перепробовал все,не могу понять в чем проблема.
вылезает при загрузке.
примерно так:
Файлы для скачивания
Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in W:\home\localhost\www\DOCUMENT_ROOT\download\index.php on line 35
21 Июль 2009, 12:45№ Наименование Описание Тип Скачать
admin:
alexey, нужен перевод? Предупреждение: переданный функции mysql_fetch_array() аргумент не является результатом запроса к базе данных. Это означает, что запрос к БД не был выполнен - скорее всего, ошибка в синтаксисе SQL-запроса. PHPFAQ.RU поможет:
21 Июль 2009, 14:31Ничего не работает! Что делать??? Поиск ошибок и отладка
При проблемах с MySQL (supplied argument is not a valid MySQL result resource) под строкой, где произошла ошибка, обязательно надо вывести на экран mysql_error() и сам запрос - чтобы его можно было выполнить из консоли или через phpmyadmin.
Дмитрий:
Респект создателю блога. Реально найти такое , надо много полазить по гуглу
26 Август 2009, 11:00sergey:
Ели наше эту статью…
Оказалось, что изменился урл… хорошо, что догадался )))
blog.webmasterschool.ru/php/250/ сейчас в гугле
http://blog.webmasterschool.ru/?p=250 реальный урл.
Теперь говорю т всей души спасибо… Скрипт пригодился
10 Сентябрь 2009, 8:31Naya:
Вот это скрипт. Круто не каждый до этого догадается.
22 Сентябрь 2009, 22:32Ala:
Хороший скриптик, я его скопировал попробую использовать
24 Сентябрь 2009, 20:43статус:
Если файлы разместить на одном хостинге, а сам сайт будет на другом, получится ли реализировать что-то подобное?
15 Октябрь 2009, 5:09Я уже около месяца сушу голову, как это можно сделать так, чтобы скачивание осуществлялось с другого хостинга, но в браузере отображался урл самого сайта.
ЗЫ. С файлами еще не работал с плоскости ПХП.
admin:
Скрипт, который будет отдавать файл с другого сервера
readfile('http://otherserver.com/files/somefile.pdf');
master:
admin, данный скрипт надо будет хорошо переписать, чтобы он отдавал файлы с другого сервера.
2 Ноябрь 2009, 14:09К примеру такие функции, как file_exists(), filesize(), filemfime(), fseec()… и хедерсам торба.
admin:
admin, данный скрипт надо будет хорошо переписать, чтобы он отдавал файлы с другого сервера
4 Ноябрь 2009, 1:42master, так что Вас останавливает?
Yury:
Спасибо. Полезная информация. А вообще наверное проще скрипт антилич (запрет скачивания файла с других доменов). Реализован на файлах например на http://acvarif.net.ru
12 Май 2010, 11:58Наверное на базе Вашего скрипта вполне можно формировать ссылки для скачивания электронных товаров в своем или не своем магазине. Будет время, если Вы не возражаете, попробую сделать его на файлах.
Yury:
Отличная статья. Такой кстати я использую для формирования временных линков на скачивание шаблонов сайтов на базе acvarif-cms.
23 Июль 2010, 20:13Виталий:
Отлично, всегда хотел нечто подобное, но руки все никак не доходили )) Спасибо
15 Ноябрь 2010, 14:11Ильдар:
Большое спасибо за скрипт! Очень помог.
20 Ноябрь 2010, 23:59Сергей:
Огромнейшее спасибо за скрипт. Разобрался и подключил. Все работает на ура
15 Декабрь 2010, 20:10Дмитрий:
Решил использовать ваш скрипт в интернет-магазине на базе ocscommerce.
16 Февраль 2011, 13:46Возникает проблема, когда уже работает функция downloads(). Выдаются ошибки Warning: Cannot add header information в каждой строке, где попытка заменить заголовок, начиная с … header( ‘HTTP/1.1 200 OK’ );…
и дальше по всем…
Файл не скачивается, а ниже этих предупреждений открывается как в блокноте…
Дмитрий:
Разобрался! Нужно алгоритм downloads() размещать в отдельном файле и кроме алгоритма там недолжно быть ничего.
16 Февраль 2011, 16:44Дмитрий О.:
Спасибо. Сам бы не написал никогда) Лень..
17 Март 2011, 0:52Man:
Как раз то, что искал. Буду генерить ссылки, отлично.
18 Март 2011, 13:22Александр:
А зачем делать через MySQL ? нагрузку и тд. Если можно сделать проще , base64_encode($url . “||” . time()); и выставить 3600сек. шифрануть , получить ссылку и делать дешифровку обратно . Думаю так даже лучше будет .
20 Октябрь 2012, 9:32