Защита от прямого скачивания для больших файлов

Илья2

Guest
Защита от прямого скачивания для больших файлов

Привет!

Сорри, если тема относится к "начинающим".

Есть следующая задача: предоставить авторизованный доступ к видео файлам, чтобы пользователь, когда прошел авторизацию и получил видео файл, он не смог бы каким либо образом "поделиться" этим с другими не авторизованными пользователями. (на стороне клиента будет запущен просмотрщик).

Мне интересно какие способы существуют.

1. "чистый PHP" - есть php скрипт "прокси", через который проходят все запросы к файлам, например download.php?file_id=111, и этот скрипт проверяет права доступа (через сессию) данного клиента и в случае если есть доступ, то выдает контент файла "на выход" -- я вижу этот как полное чтение файла и вывод его.

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

2. "apache" htaccess метод - при авторизации пользователя, на папку, где хранятся файлы, генерировать .htaccess в котором прописан доступ только для IP данного клиента, т.е. ограничение на IP - после окончания времени жизни сессии - удаление этого IP.

3. гипотетический apache фильтр метод - не знаю может быть уже реализована такая возможность в каких либо apache модулях: на какую либо папку, либо по RegExp маске на url (по типу mod_rewrite) запускается фильтр (допустип тотже php скрипт который проверяет доступ) и если фильтр возвращает положительный ответ, до доступ проходит, если отрицательный - то ошибка.

мне более нормальным видится 1ый способ, но я не понимаю как преодолеть проблему большого объема файла.


Илья
 

Линк

Guest
Я бы предпочел первый вариант
Создали сессию, положили в нее максимум инфы: (user agent например. и REMOTE_ADDR который нельзя подделать )
таким образом скачайть файл можно будет только с этого IP

что касаеться больших файлов, то читать в память не обязательно
можно его открыть и читать по частям:
[m]fopen[/m]
[m]fgets[/m]

только для больших файлов надо поддерживать докачку
но это просто реализуеться


второй вариант тоже интересен.
но первый, imho, красивее
 

neko

tеam neko
что значит "немог поделиться"?

если некто может файл скачать, значит он может с ним потом делать все что угодно.

если речь идет о "немог поделиться ссылкой" то подойдет и 1 и 2
 

Yurik

/dev/null
2 намного красивее.
через пхп добавлять туда строки
Allow ip.ip.ip.ip # 2004-05-14 21:00:00

а в комменте указывать до какого времени действует разрешение

и написать garbage collector который по крону скажем каждые полчаса чистит те строки в которых закомментированная дата меньше текущей

можно этот же вариант но с .htpasswd реализовать и давать ссылку
href='http://tempuser:p[email protected]/downloads/file.mpeg"

а через 1 вариант это в любом случае криво
 

Yurik

/dev/null
neko для файлов больше пару метров поддержка докачки/закачки частями IS MUST это раз. А во-вторых readfile() как ни странно считывает файл в оперативу а потом пихает на стандартный вывод

-~{}~ 15.05.04 09:43:

>положили в нее максимум инфы: (user agent например
Линк: большие файлы мало кто броузером качает, обычно линк пихают в менеджеры закачки
 

rudik

Developer
IMHO Менеджеры тоже имеют USER AGENT строку. Если качают по HTTP.
 

Линк

Guest
href='http://tempuser:p[email protected]/downloads/file.mpeg"
эту ссылку скоро отключат

А во-вторых readfile() как ни странно считывает файл в оперативу а потом пихает на стандартный вывод
я ж скаал [m]fopen[/m] [m]fgets[/m]


ольшие файлы мало кто броузером качает, обычно линк пихают в менеджеры закачки
ну и какая разница?;)
 

neko

tеam neko
Originally posted by Линк
я ж скаал [m]fopen[/m] [m]fgets[/m]
да, можно сделать так и с докачкой если обрабатывать range итд итп

просто вариант с модификацей htaccess мне ненравится потому что

во-первых это вообще плохая идея править таким образом конфигурационные файлы

во-вторых ограничение по ip, это вообще не ограничение, если автору вопроса хочется ограничить передачу ссылок между пользователями
 

Линк

Guest
а чем тут сессии помогут?
только если их принудительно в куках держать
но и куки можно передавать
 

trigger

Guest
Я за 1 вариант. fopen(), fseek(), fgets().
Чем проще, логичнее или безопаснее второй вариант? Разве что за докачкой будет следит тот, кто это умеет делать, те apache.
 

Yurik

/dev/null
>Создали сессию, положили в нее максимум инфы

>Менеджеры тоже имеют USER AGENT строку

>ну и какая разница?

разница что сессия откроется для броузера, а качать юзер будеть качалкой

>эту ссылку скоро отключат
на это и расчитан метод

>я ж скаал fopen fgets
а какая по сути разница?

>Чем проще, логичнее или безопаснее второй вариант?
ты реализуй такую закачку для файлов от 50 Мб и выше а тогда придешь сюда и будешь говорить и логике, простоте и безопасности
 

trigger

Guest
Yurik
>Чем проще, логичнее или безопаснее второй вариант?
ты реализуй такую закачку для файлов от 50 Мб и выше а тогда придешь сюда и будешь говорить и логике, простоте и безопасности

О да, сильный аргумент, сильный, ничего не скажешь...

И все же я нормально спросил. Может, у кого-нибудь еще есть идеи?
Я имею ввиду менеджеры закачек типа Reget, которые реконнектятся при обрыве коннекта. То есть отдали 100кб и отключились. Имхо, это лучше, чем править .htaccess.

-~{}~ 24.05.04 20:26:

Хотя, если не надо сильно следить за солидностью ( логичностью, стабильностью ), то можно и через .htaccess
 

MagicGTS

Новичок
Для организации отдачи больших файло не сложно применить такую конструкцию:
PHP:
$chain_size=1024*64;
$workFileName = <filename>;
$fd = fopen (<filename>, "rb");
$workFileSize = filesize (<filename>);
$full_part=(int)($workFileSize/$chain_size);
$patch_part=$workFileSize-$full_part*$chain_size;
while($full_part>=0)
{
	$contents = fread ($fd, $chain_size);
	echo $contents;
	$full_part=$full_part-1;
}
$contents = fread ($fd, $patch_part);
echo $contents;
		
fclose ($fd);
 

IntenT

SkyDiver
MagicGTS
А где здесь механизм докачки реализован???

trigger
Разве что за докачкой будет следит тот, кто это умеет делать, те apache.
Не говори того, в чем не разбираешься. Если файл отдается скриптом, то апач никак не сможет организовать его докачку.
 

desperado

Новичок
А где здесь механизм докачки реализован???
похоже на правду?
PHP:
<?
function download_file($file = NULL, $speed_limit = 1024, $resume = true, $send_errors = false){
//return:: 0 - ok \ 1 - $file is_null \ 2 - forbidden \ 3 - 404 Error
  if(is_null($file)){ 
    return 1;
  }else{
    $file_name = basename($file); 
    $speed_limit = intval($speed_limit); 
    if($speed_limit<0) $speed_limit = 1024; 

    $running_time = 0; 
    $begin_time = time(); 

    set_time_limit(300); 

    if(file_exists($file))
    { 
      if( false !== ($file_hand = fopen($file, "rb")) )
      { 
        $file_size = filesize($file); 
        $file_date = date("D, d M Y H:i:s T",filemtime($file));
        if(preg_match("/bytes=(\d+)-/", $_SERVER["HTTP_RANGE"],$range) && $resume == true)
        { 
          header("HTTP/1.1 206 Partial Content"); 
          $offset = $file_size - intval($range[1]); 
        }else{ 
          header("HTTP/1.1 200 OK"); 
          $offset = 0; 
        } 

        $data_start = $offset; 
        $data_end = $file_size - 1; 
        $etag = md5($file.$file_size.$file_date); 

        fseek($file_hand, $data_start); 

        header("Content-Disposition: attachment; filename=".$file_name); 
        header("Last-Modified: ".$file_date); 
        header("ETag: \"".$etag."\""); 
        if($resume == true) header("Accept-Ranges: bytes"); 
        header("Content-Length: ".($file_size-$data_start)); 
        header("Content-Range: bytes ".$data_start."-".$data_end."/".$file_size); 
        header("Content-type: application/octet-stream"); 

        while(!feof($file_hand) && (connection_status()==0))
        { 
          print fread($file_hand,$speed_limit); 
          flush(); 
          sleep(1); 
          $running_time = time() - $begin_time; 
          if($running_time>240)
          { 
            set_time_limit(300); 
            $begin_time = time(); 
          } 
        }
        fclose ($file_hand); 
        return 0; 
      }
      else
      {
        if($send_errors == true) header ("HTTP/1.0 403 Forbidden"); 
        return 2; 
      }
    }else{
      if($send_errors == true) header("HTTP/1.0 404 Not Found"); 
      return 3; 
    } 
  }
} 
?>
отдовались исошники в локал-нете... думаю, не проблема будет дописать сюда самому ограничение по кол-ву пользователей \ сессий на файл на все, но это было реализовано вне этой функции

-~{}~ 16.06.04 19:52:

пофиксил:
header("Content-Disposition: attachment; filename=".$file_hand);

header("Content-Disposition: attachment; filename=".$file_name);
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Автор оригинала: desperado
похоже на правду?
-~{}~ 16.06.04 19:52:

пофиксил:
header("Content-Disposition: attachment; filename=".$file_hand);

header("Content-Disposition: attachment; filename=".$file_name);
$file_name не определена
наверное, имелось ввиду, что вместо
$path_name = basename($file);
написано
PHP:
$file_name = basename($file);
т.к. путь возвращается ф-ей dirname()
 

MagicGTS

Новичок
У меня похожий скрипт очень активно жрёт память (примерно половину от скачиваемого файла, то есть если файл 1гб, то он отъест 0.5 гб памяти!).
 

desperado

Новичок
Автор оригинала: grigori
$file_name не определена
наверное, имелось ввиду, что вместо

написано
PHP:
$file_name = basename($file);
т.к. путь возвращается ф-ей dirname()
сенкс за поправку, после глазного как коматоженный хожу весь день

-~{}~ 16.06.04 22:17:

и еще, там
$data_start = $offset;

-~{}~ 16.06.04 22:35:

только что проверил скрипт... нормально держит в районе мегобайта.

если ставить лимит в 3, то средняя будет в районе 2.85. при большей скорости, думаю будут еще большие провалы.

при установке лимита скорости в 3 мегабайта, съедаемая память была около 5-ти мегабайт и, можно скзать, была стабильна в течение всего времени скачивания.
 

Илья2

Guest
Привет!
спасибо за ответы!

напомню, что в моем случае веб-клиентом был все же не браузер, а WindowsMediaPlayer. Т.е. получается, что работа с куками (и соотв. с информацией на стороне клиента) отменяется :) и не известно как он поддерживает докачку, надо изучать более внимательно протокол его обмена. Возможно, что вышеприведенный фрагмент будет работать.

Я пока решил задачу с помощью htaccess, мне надо проверить 2 условия - пришли с правильного IP, и веб-клиент, не Браузер (для предотвращения прямого скачивания по ссылке), конечно правильнее было бы проверять на клиента WindowsMediaPlayer - 'NSPlayer', но в директиве SetEnvIf я не смог получить отрицания регекспа :)

тут про SetEnvIf : http://httpd.apache.org/docs/mod/mod_setenvif.html#setenvif

сам php класс, который генерирует htaccess файл (он у меня поставлен на крон):

PHP:
load_class( 'File/File.php' );

class HTAccessFile{

  function HTAccessFile(){
    $this->_file = new File();
  }

  // это путь до .htaccess файла
  function setFileName( $file_name ){
    $this->_file->setFileName( $file_name );
  }

  // это объект который работает с БД
  function setCodeContainer( &$codeContainer ){
    $this->_codeContainer = &$codeContainer;
  }

  function generate(){
    // запрашиваем IP клиентов, которых надо пускать
    $ip_array = $this->_codeContainer->getActiveIPs();
    $str = '';
    $str .= $this->_getHeader();
    $str .= $this->_getAccessLines( $ip_array );
    $this->_file->setData( $str );
    $this->_file->save();
  }

  function _getHeader(){
    $str = 'Order deny,allow'."\n";
    $str .= 'Deny from all'."\n";
    return $str;
  }

  function _getAccessLines( $ip_array ){
    $str = '';
    $i = 1;
    foreach( $ip_array as $ip ){
      $ip_str = trim( $ip );
      if ( !empty( $ip_str ) ){
        $str .= 'SetEnvIf Remote_Addr '.$this->_regIt($ip).' let_me_in'.$i."\n";
        $str .= 'SetEnvIf User-Agent ^Mozilla !let_me_in'.$i."\n";
        $str .= 'Allow from env=let_me_in'.$i."\n";
      }
      $i++;
    }
    return $str;
  }

  function _regIt( $ip ){
    $ip = str_replace( '.', '\.', $ip );
    $ip = '^'.$ip.'$';
    return $ip;
  }

}
т.е. для каждого клиент которого надо пустить, формируется переменная в htaccess которая в начале выставляется если IP текущего веб-клиента совпадает, и потом сбрасывается, если веб-клиент Mozilla (т.е. IE и т.д.)

конечно это не 100% защита, но все же.

хотя вот еще раз сейчас посмотрел, скорее всего возможно использовать одну переменную, т.е.
SetEnvIf Remote_Addr ^127\.0\.0\.1$ let_me_in
SetEnvIf Remote_Addr ^127\.0\.0\.2$ let_me_in
# etc
SetEnvIf User-Agent ^Mozilla !let_me_in
Allow from env=let_me_in


Илья
 
Сверху