PHP中有部分文件下载的好实现吗?

发布于 2024-08-04 18:06:16 字数 271 浏览 5 评论 0原文

我正在编写一个允许用户下载文件的 PHP 脚本。基本上,这个想法是防止文件被下载超过 X 次,因为它是付费内容,并且链接不应该传播。

由于文件相当大,因此最好实现恢复。我已阅读标准,但它是相当长并且允许一定的灵活性。由于我需要快速完成它,因此我更喜欢此功能的稳定且经过测试的实现。

谁能指出我这样的脚本吗?

I'm writing a PHP script that allows the user to download a file. Basically the idea is to prevent the file being downloaded more than X times, since it is paid content, and the link should not be spread around.

Since the files will be pretty large, it should be good to implement resuming. I've read the standard, but it's pretty long and allows for some flexibility. Since I need to get it done quickly, I'd prefer a stable, tested implementation of this feature.

Can anyone point me to such a a script?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

疑心病 2024-08-11 18:06:16

看来我自己找到了我需要的东西。为了让其他人可以从中受益,以下是链接:http://www.coneural.org/florian/papers/04_byteserving.php

以防万一原始页面停止工作(脚本已经很旧了),这里是它的副本:

<?php 
/*

The following byte serving code is (C) 2004 Razvan Florian. You may find the latest version at 
http://www.coneural.org/florian/papers/04_byteserving.php

*/
function set_range($range, $filesize, &$first, &$last){
  /*
  Sets the first and last bytes of a range, given a range expressed as a string 
  and the size of the file.

  If the end of the range is not specified, or the end of the range is greater 
  than the length of the file, $last is set as the end of the file.

  If the begining of the range is not specified, the meaning of the value after 
  the dash is "get the last n bytes of the file".

  If $first is greater than $last, the range is not satisfiable, and we should 
  return a response with a status of 416 (Requested range not satisfiable).

  Examples:
  $range='0-499', $filesize=1000 => $first=0, $last=499 .
  $range='500-', $filesize=1000 => $first=500, $last=999 .
  $range='500-1200', $filesize=1000 => $first=500, $last=999 .
  $range='-200', $filesize=1000 => $first=800, $last=999 .

  */
  $dash=strpos($range,'-');
  $first=trim(substr($range,0,$dash));
  $last=trim(substr($range,$dash+1));
  if ($first=='') {
    //suffix byte range: gets last n bytes
    $suffix=$last;
    $last=$filesize-1;
    $first=$filesize-$suffix;
    if($first<0) $first=0;
  } else {
    if ($last=='' || $last>$filesize-1) $last=$filesize-1;
  }
  if($first>$last){
    //unsatisfiable range
    header("Status: 416 Requested range not satisfiable");
    header("Content-Range: */$filesize");
    exit;
  }
}

function buffered_read($file, $bytes, $buffer_size=1024){
  /*
  Outputs up to $bytes from the file $file to standard output, $buffer_size bytes at a time.
  */
  $bytes_left=$bytes;
  while($bytes_left>0 && !feof($file)){
    if($bytes_left>$buffer_size)
      $bytes_to_read=$buffer_size;
    else
      $bytes_to_read=$bytes_left;
    $bytes_left-=$bytes_to_read;
    $contents=fread($file, $bytes_to_read);
    echo $contents;
    flush();
  }
}

function byteserve($filename){
  /*
  Byteserves the file $filename.  

  When there is a request for a single range, the content is transmitted 
  with a Content-Range header, and a Content-Length header showing the number 
  of bytes actually transferred.

  When there is a request for multiple ranges, these are transmitted as a 
  multipart message. The multipart media type used for this purpose is 
  "multipart/byteranges".
  */

  $filesize=filesize($filename);
  $file=fopen($filename,"rb");

  $ranges=NULL;
  if ($_SERVER['REQUEST_METHOD']=='GET' && isset($_SERVER['HTTP_RANGE']) && $range=stristr(trim($_SERVER['HTTP_RANGE']),'bytes=')){
    $range=substr($range,6);
    $boundary='g45d64df96bmdf4sdgh45hf5';//set a random boundary
    $ranges=explode(',',$range);
  }

  if($ranges && count($ranges)){
    header("HTTP/1.1 206 Partial content");
    header("Accept-Ranges: bytes");
    if(count($ranges)>1){
      /*
      More than one range is requested. 
      */

      //compute content length
      $content_length=0;
      foreach ($ranges as $range){
        set_range($range, $filesize, $first, $last);
        $content_length+=strlen("\r\n--$boundary\r\n");
        $content_length+=strlen("Content-type: application/pdf\r\n");
        $content_length+=strlen("Content-range: bytes $first-$last/$filesize\r\n\r\n");
        $content_length+=$last-$first+1;          
      }
      $content_length+=strlen("\r\n--$boundary--\r\n");

      //output headers
      header("Content-Length: $content_length");
      //see http://httpd.apache.org/docs/misc/known_client_problems.html for an discussion of x-byteranges vs. byteranges
      header("Content-Type: multipart/x-byteranges; boundary=$boundary");

      //output the content
      foreach ($ranges as $range){
        set_range($range, $filesize, $first, $last);
        echo "\r\n--$boundary\r\n";
        echo "Content-type: application/pdf\r\n";
        echo "Content-range: bytes $first-$last/$filesize\r\n\r\n";
        fseek($file,$first);
        buffered_read ($file, $last-$first+1);          
      }
      echo "\r\n--$boundary--\r\n";
    } else {
      /*
      A single range is requested.
      */
      $range=$ranges[0];
      set_range($range, $filesize, $first, $last);  
      header("Content-Length: ".($last-$first+1) );
      header("Content-Range: bytes $first-$last/$filesize");
      header("Content-Type: application/pdf");  
      fseek($file,$first);
      buffered_read($file, $last-$first+1);
    }
  } else{
    //no byteserving
    header("Accept-Ranges: bytes");
    header("Content-Length: $filesize");
    header("Content-Type: application/pdf");
    readfile($filename);
  }
  fclose($file);
}

function serve($filename, $download=0){
  //Just serves the file without byteserving
  //if $download=true, then the save file dialog appears
  $filesize=filesize($filename);
  header("Content-Length: $filesize");
  header("Content-Type: application/pdf");
  $filename_parts=pathinfo($filename);
  if($download) header('Content-disposition: attachment; filename='.$filename_parts['basename']);
  readfile($filename);
}

//unset magic quotes; otherwise, file contents will be modified
set_magic_quotes_runtime(0);

//do not send cache limiter header
ini_set('session.cache_limiter','none');


$filename='myfile.pdf'; //this is the PDF file that will be byteserved
byteserve($filename); //byteserve it!
?>

Seems that I found what I needed myself. So that other may benefit from this, here is the link: http://www.coneural.org/florian/papers/04_byteserving.php

And just in case the original page stops to work (the script is pretty old already), here is a copy of it:

<?php 
/*

The following byte serving code is (C) 2004 Razvan Florian. You may find the latest version at 
http://www.coneural.org/florian/papers/04_byteserving.php

*/
function set_range($range, $filesize, &$first, &$last){
  /*
  Sets the first and last bytes of a range, given a range expressed as a string 
  and the size of the file.

  If the end of the range is not specified, or the end of the range is greater 
  than the length of the file, $last is set as the end of the file.

  If the begining of the range is not specified, the meaning of the value after 
  the dash is "get the last n bytes of the file".

  If $first is greater than $last, the range is not satisfiable, and we should 
  return a response with a status of 416 (Requested range not satisfiable).

  Examples:
  $range='0-499', $filesize=1000 => $first=0, $last=499 .
  $range='500-', $filesize=1000 => $first=500, $last=999 .
  $range='500-1200', $filesize=1000 => $first=500, $last=999 .
  $range='-200', $filesize=1000 => $first=800, $last=999 .

  */
  $dash=strpos($range,'-');
  $first=trim(substr($range,0,$dash));
  $last=trim(substr($range,$dash+1));
  if ($first=='') {
    //suffix byte range: gets last n bytes
    $suffix=$last;
    $last=$filesize-1;
    $first=$filesize-$suffix;
    if($first<0) $first=0;
  } else {
    if ($last=='' || $last>$filesize-1) $last=$filesize-1;
  }
  if($first>$last){
    //unsatisfiable range
    header("Status: 416 Requested range not satisfiable");
    header("Content-Range: */$filesize");
    exit;
  }
}

function buffered_read($file, $bytes, $buffer_size=1024){
  /*
  Outputs up to $bytes from the file $file to standard output, $buffer_size bytes at a time.
  */
  $bytes_left=$bytes;
  while($bytes_left>0 && !feof($file)){
    if($bytes_left>$buffer_size)
      $bytes_to_read=$buffer_size;
    else
      $bytes_to_read=$bytes_left;
    $bytes_left-=$bytes_to_read;
    $contents=fread($file, $bytes_to_read);
    echo $contents;
    flush();
  }
}

function byteserve($filename){
  /*
  Byteserves the file $filename.  

  When there is a request for a single range, the content is transmitted 
  with a Content-Range header, and a Content-Length header showing the number 
  of bytes actually transferred.

  When there is a request for multiple ranges, these are transmitted as a 
  multipart message. The multipart media type used for this purpose is 
  "multipart/byteranges".
  */

  $filesize=filesize($filename);
  $file=fopen($filename,"rb");

  $ranges=NULL;
  if ($_SERVER['REQUEST_METHOD']=='GET' && isset($_SERVER['HTTP_RANGE']) && $range=stristr(trim($_SERVER['HTTP_RANGE']),'bytes=')){
    $range=substr($range,6);
    $boundary='g45d64df96bmdf4sdgh45hf5';//set a random boundary
    $ranges=explode(',',$range);
  }

  if($ranges && count($ranges)){
    header("HTTP/1.1 206 Partial content");
    header("Accept-Ranges: bytes");
    if(count($ranges)>1){
      /*
      More than one range is requested. 
      */

      //compute content length
      $content_length=0;
      foreach ($ranges as $range){
        set_range($range, $filesize, $first, $last);
        $content_length+=strlen("\r\n--$boundary\r\n");
        $content_length+=strlen("Content-type: application/pdf\r\n");
        $content_length+=strlen("Content-range: bytes $first-$last/$filesize\r\n\r\n");
        $content_length+=$last-$first+1;          
      }
      $content_length+=strlen("\r\n--$boundary--\r\n");

      //output headers
      header("Content-Length: $content_length");
      //see http://httpd.apache.org/docs/misc/known_client_problems.html for an discussion of x-byteranges vs. byteranges
      header("Content-Type: multipart/x-byteranges; boundary=$boundary");

      //output the content
      foreach ($ranges as $range){
        set_range($range, $filesize, $first, $last);
        echo "\r\n--$boundary\r\n";
        echo "Content-type: application/pdf\r\n";
        echo "Content-range: bytes $first-$last/$filesize\r\n\r\n";
        fseek($file,$first);
        buffered_read ($file, $last-$first+1);          
      }
      echo "\r\n--$boundary--\r\n";
    } else {
      /*
      A single range is requested.
      */
      $range=$ranges[0];
      set_range($range, $filesize, $first, $last);  
      header("Content-Length: ".($last-$first+1) );
      header("Content-Range: bytes $first-$last/$filesize");
      header("Content-Type: application/pdf");  
      fseek($file,$first);
      buffered_read($file, $last-$first+1);
    }
  } else{
    //no byteserving
    header("Accept-Ranges: bytes");
    header("Content-Length: $filesize");
    header("Content-Type: application/pdf");
    readfile($filename);
  }
  fclose($file);
}

function serve($filename, $download=0){
  //Just serves the file without byteserving
  //if $download=true, then the save file dialog appears
  $filesize=filesize($filename);
  header("Content-Length: $filesize");
  header("Content-Type: application/pdf");
  $filename_parts=pathinfo($filename);
  if($download) header('Content-disposition: attachment; filename='.$filename_parts['basename']);
  readfile($filename);
}

//unset magic quotes; otherwise, file contents will be modified
set_magic_quotes_runtime(0);

//do not send cache limiter header
ini_set('session.cache_limiter','none');


$filename='myfile.pdf'; //this is the PDF file that will be byteserved
byteserve($filename); //byteserve it!
?>
贩梦商人 2024-08-11 18:06:16

您应该使用 PEAR HTTP_Download。它非常易于使用,并且允许仅恢复下载文件:

http://pear.php.net/manual/en/package.http.http-download.intro.php

You should be using PEAR HTTP_Download. It is pretty easy to use and it allows download resuming just file:

http://pear.php.net/manual/en/package.http.http-download.intro.php

清晨说晚安 2024-08-11 18:06:16

基于此:

http: //w-shadow.com/blog/2007/08/12/how-to-force-file-download-with-php/

(你也可以使用)

我做了一个小库,它可以PECL http_send_file 扩展的作用:

http://php.net/manual/en /function.http-send-file.php

(您也可以使用)

该库类似于 http_send_file,但如果您没有安装 PECL 库的选项,您可以使用 http-send-文件库:

https://github.com/diversen/http-send-file

Based on this:

http://w-shadow.com/blog/2007/08/12/how-to-force-file-download-with-php/

(which you also could use)

I've made a small lib that does what PECL http_send_file extension does:

http://php.net/manual/en/function.http-send-file.php

(which you also could use)

The lib resembles the http_send_file, but if you don't have the option of installing the PECL lib, you could use the http-send-file lib:

https://github.com/diversen/http-send-file

蓝戈者 2024-08-11 18:06:16

请参阅 https://www.php.net/manual/en/function.fread .php

另一种方法是让 Web 服务器可以通过重定向到相关文件来处理 http。

PHP 脚本可以在调用 header("Location $urltofile"); 之前执行任何所需的检查(安全性、身份验证、验证文件、增加下载计数)以及任何其他任务。

我用阿帕奇测试了这个。中断/恢复下载工作。服务器的 mime 类型配置将决定客户端的行为。对于 apache,如果 mime.types 中的默认值不合适,则 mod_mime 的配置指令可以放入要下载的文件目录中的 .htaccess 文件中。如果确实有必要,这些甚至可以由 PHP 脚本在重定向之前编写。

See https://www.php.net/manual/en/function.fread.php

An alternative is to let the web server can handle http by redirecting to the file in question.

A PHP script can do any checks needed (security, authentication, validate the file, incrementing the download count) and any other tasks before calling header("Location $urltofile");

I tested this with apache. Interrupt/resume download works. The server's mime type configuration will determine client behavior. For apache, if defaults in mime.types are not suitable, configuration directives for mod_mime could go in a .htaccess file in the directory of the file to download. If really necessary, these could even by written by the PHP script before it redirects.

风尘浪孓 2024-08-11 18:06:16

Perhaps instead of implementing web server in a web server (yo dawg!) you could use mod trigger before download in lighttpd or mod X-Sendfile available for both lighttpd and Apache2?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文