[Filebin-general] [PATCH] Implement rangeDownload() as driver and provide sendfile implementations for Nginx and Lighttpd

Pierre Schmitz pierre at archlinux.de
Mon Sep 23 07:47:40 CEST 2013


* The rangeDownload() function has been moved to libraries/Ddownload/drivers/Ddownload_php.php
* The nginx and lighttpd drivers can be set via $config['download_driver']

Signed-off-by: Pierre Schmitz <pierre at archlinux.de>
---
 application/config/config.php                      |  16 +++
 application/controllers/file.php                   |   6 +-
 application/helpers/filebin_helper.php             |  99 ------------------
 application/libraries/Ddownload/Ddownload.php      |  34 +++++++
 .../Ddownload/drivers/Ddownload_lighttpd.php       |  27 +++++
 .../Ddownload/drivers/Ddownload_nginx.php          |  30 ++++++
 .../libraries/Ddownload/drivers/Ddownload_php.php  | 111 +++++++++++++++++++++
 7 files changed, 222 insertions(+), 101 deletions(-)
 create mode 100644 application/libraries/Ddownload/Ddownload.php
 create mode 100644 application/libraries/Ddownload/drivers/Ddownload_lighttpd.php
 create mode 100644 application/libraries/Ddownload/drivers/Ddownload_nginx.php
 create mode 100644 application/libraries/Ddownload/drivers/Ddownload_php.php

diff --git a/application/config/config.php b/application/config/config.php
index 388776c..04fcc85 100644
--- a/application/config/config.php
+++ b/application/config/config.php
@@ -411,6 +411,22 @@ if (extension_loaded("ldap")) {
 // possible values: production, development
 $config['environment'] = "production";
 
+// This sets the download implementation. Possible values are php, nginx and lighttpd
+// The nginx and lighttpd drivers make use of the server's sendfile feature.
+$config['download_driver'] = 'php';
+// The lighttpd driver requires the following directive to be set in your fastcgi.server configuration:
+//	"allow-x-send-file" => "enable"
+// See http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModFastCGI#X-Sendfile
+//
+// When using the nginx download driver you need to define an internal location
+// from which nginx will serve your uploads:
+//	location ^~ /protected-uploads/ {
+//		internal;
+//		alias <upload_path>/;
+//	}
+// See http://wiki.nginx.org/X-accel
+$config['download_nginx_location'] = '/protected-uploads';
+
 if (file_exists(FCPATH.'application/config/config-local.php')) {
 	include FCPATH.'application/config/config-local.php';
 }
diff --git a/application/controllers/file.php b/application/controllers/file.php
index 98ccae8..657ba16 100644
--- a/application/controllers/file.php
+++ b/application/controllers/file.php
@@ -99,10 +99,12 @@ class File extends MY_Controller {
 			exit();
 		}
 
+		$this->load->driver("ddownload");
+
 		// user wants the plain file
 		if ($lexer == 'plain') {
 			handle_etag($etag);
-			rangeDownload($file, $filedata["filename"], "text/plain");
+			$this->ddownload->serveFile($file, $filedata["filename"], "text/plain");
 			exit();
 		}
 
@@ -123,7 +125,7 @@ class File extends MY_Controller {
 				header("$header_name: allow 'none'; img-src *; media-src *; font-src *; style-src * 'unsafe-inline'; script-src 'none'; object-src *; frame-src 'none'; ");
 			}
 			handle_etag($etag);
-			rangeDownload($file, $filedata["filename"], $filedata["mimetype"]);
+			$this->ddownload->serveFile($file, $filedata["filename"], $filedata["mimetype"]);
 			exit();
 		}
 
diff --git a/application/helpers/filebin_helper.php b/application/helpers/filebin_helper.php
index bed696c..a4e18f2 100644
--- a/application/helpers/filebin_helper.php
+++ b/application/helpers/filebin_helper.php
@@ -20,105 +20,6 @@ function format_bytes($size)
 	}
 }
 
-// Original source: http://www.phpfreaks.com/forums/index.php?topic=198274.msg895468#msg895468
-function rangeDownload($file, $filename, $type)
-{
-	$fp = @fopen($file, 'r');
-
-	$size	= filesize($file); // File size
-	$length = $size;	   // Content length
-	$start	= 0;		   // Start byte
-	$end	= $size - 1;	   // End byte
-	// Now that we've gotten so far without errors we send the accept range header
-	/* At the moment we only support single ranges.
-	 * Multiple ranges requires some more work to ensure it works correctly
-	 * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
-	 *
-	 * Multirange support annouces itself with:
-	 * header('Accept-Ranges: bytes');
-	 *
-	 * Multirange content must be sent with multipart/byteranges mediatype,
-	 * (mediatype = mimetype)
-	 * as well as a boundry header to indicate the various chunks of data.
-	 */
-	header("Accept-Ranges: 0-$length");
-	// header('Accept-Ranges: bytes');
-	// multipart/byteranges
-	// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
-	if (isset($_SERVER['HTTP_RANGE']))
-	{
-		$c_start = $start;
-		$c_end	 = $end;
-		// Extract the range string
-		list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
-		// Make sure the client hasn't sent us a multibyte range
-		if (strpos($range, ',') !== false)
-		{
-			// (?) Shoud this be issued here, or should the first
-			// range be used? Or should the header be ignored and
-			// we output the whole content?
-			header('HTTP/1.1 416 Requested Range Not Satisfiable');
-			header("Content-Range: bytes $start-$end/$size");
-			// (?) Echo some info to the client?
-			exit;
-		}
-		// If the range starts with an '-' we start from the beginning
-		// If not, we forward the file pointer
-		// And make sure to get the end byte if spesified
-		if ($range{0} == '-')
-		{
-			// The n-number of the last bytes is requested
-			$c_start = $size - substr($range, 1);
-		}
-		else
-		{
-			$range	= explode('-', $range);
-			$c_start = $range[0];
-			$c_end	 = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
-		}
-		/* Check the range and make sure it's treated according to the specs.
-		 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
-		 */
-		// End bytes can not be larger than $end.
-		$c_end = ($c_end > $end) ? $end : $c_end;
-		// Validate the requested range and return an error if it's not correct.
-		if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size)
-		{
-			header('HTTP/1.1 416 Requested Range Not Satisfiable');
-			header("Content-Range: bytes $start-$end/$size");
-			// (?) Echo some info to the client?
-			exit;
-		}
-		$start	= $c_start;
-		$end	= $c_end;
-		$length = $end - $start + 1; // Calculate new content length
-		fseek($fp, $start);
-		header('HTTP/1.1 206 Partial Content');
-		// Notify the client the byte range we'll be outputting
-		header("Content-Range: bytes $start-$end/$size");
-	}
-	header("Content-Length: $length");
-	header("Content-disposition: inline; filename=\"".$filename."\"\n");
-	header("Content-Type: ".$type."\n");
-
-	// Start buffered download
-	$buffer = 1024 * 8;
-	while(!feof($fp) && ($p = ftell($fp)) <= $end)
-	{
-		if ($p + $buffer > $end)
-		{
-			// In case we're only outputtin a chunk, make sure we don't
-			// read past the length
-			$buffer = $end - $p + 1;
-		}
-		set_time_limit(0); // Reset time limit for big files
-		echo fread($fp, $buffer);
-		flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
-	}
-
-	fclose($fp);
-}
-
 function even_odd($reset = false)
 {
 	static $counter = 1;
diff --git a/application/libraries/Ddownload/Ddownload.php b/application/libraries/Ddownload/Ddownload.php
new file mode 100644
index 0000000..808dfe7
--- /dev/null
+++ b/application/libraries/Ddownload/Ddownload.php
@@ -0,0 +1,34 @@
+<?php
+/*
+ * Copyright 2013 Pierre Schmitz <pierre at archlinux.de>
+ *
+ * Licensed under AGPLv3
+ * (see COPYING for full license text)
+ *
+ */
+
+abstract class Ddownload_Driver extends CI_Driver {
+
+	abstract public function serveFile($file, $filename, $type);
+}
+
+class Ddownload extends CI_Driver_Library {
+
+	protected $_adapter = null;
+
+	protected $valid_drivers = array(
+		'ddownload_php', 'ddownload_nginx', 'ddownload_lighttpd'
+	);
+
+	function __construct()
+	{
+		$CI =& get_instance();
+
+		$this->_adapter = $CI->config->item('download_driver');
+	}
+
+	public function serveFile($file, $filename, $type)
+	{
+		$this->{$this->_adapter}->serveFile($file, $filename, $type);
+	}
+}
diff --git a/application/libraries/Ddownload/drivers/Ddownload_lighttpd.php b/application/libraries/Ddownload/drivers/Ddownload_lighttpd.php
new file mode 100644
index 0000000..31db4d3
--- /dev/null
+++ b/application/libraries/Ddownload/drivers/Ddownload_lighttpd.php
@@ -0,0 +1,27 @@
+<?php
+/*
+ * Copyright 2013 Pierre Schmitz <pierre at archlinux.de>
+ *
+ * Licensed under AGPLv3
+ * (see COPYING for full license text)
+ *
+ */
+
+class Ddownload_lighttpd extends Ddownload_Driver {
+
+	public function serveFile($file, $filename, $type)
+	{
+		$CI =& get_instance();
+		$upload_path = $CI->config->item('upload_path');
+
+		if (strpos($file, $upload_path) !== 0) {
+			show_error('Invalid file path');
+			return;
+		}
+
+		header('Content-disposition: inline; filename="'.$filename."\"\n");
+		header('Content-Type: '.$type."\n");
+		header('X-Sendfile: '.$file."\n");
+	}
+
+}
diff --git a/application/libraries/Ddownload/drivers/Ddownload_nginx.php b/application/libraries/Ddownload/drivers/Ddownload_nginx.php
new file mode 100644
index 0000000..5fb6ffa
--- /dev/null
+++ b/application/libraries/Ddownload/drivers/Ddownload_nginx.php
@@ -0,0 +1,30 @@
+<?php
+/*
+ * Copyright 2013 Pierre Schmitz <pierre at archlinux.de>
+ *
+ * Licensed under AGPLv3
+ * (see COPYING for full license text)
+ *
+ */
+
+class Ddownload_nginx extends Ddownload_Driver {
+
+	public function serveFile($file, $filename, $type)
+	{
+		$CI =& get_instance();
+		$upload_path = $CI->config->item('upload_path');
+		$download_location = $CI->config->item('download_nginx_location');
+
+		if (strpos($file, $upload_path) === 0) {
+			$file_path = substr($file, strlen($upload_path));
+		} else {
+			show_error('Invalid file path');
+			return;
+		}
+
+		header('Content-disposition: inline; filename="'.$filename."\"\n");
+		header('Content-Type: '.$type."\n");
+		header('X-Accel-Redirect: '.$download_location.$file_path."\n");
+	}
+
+}
diff --git a/application/libraries/Ddownload/drivers/Ddownload_php.php b/application/libraries/Ddownload/drivers/Ddownload_php.php
new file mode 100644
index 0000000..344db53
--- /dev/null
+++ b/application/libraries/Ddownload/drivers/Ddownload_php.php
@@ -0,0 +1,111 @@
+<?php
+/*
+ * Copyright 2013 Florian "Bluewind" Pritz <bluewind at server-speed.net>
+ *
+ * Licensed under AGPLv3
+ * (see COPYING for full license text)
+ *
+ */
+
+class Ddownload_php extends Ddownload_Driver {
+
+	// Original source: http://www.phpfreaks.com/forums/index.php?topic=198274.msg895468#msg895468
+	public function serveFile($file, $filename, $type)
+	{
+		$fp = @fopen($file, 'r');
+
+		$size	= filesize($file); // File size
+		$length = $size;	   // Content length
+		$start	= 0;		   // Start byte
+		$end	= $size - 1;	   // End byte
+		// Now that we've gotten so far without errors we send the accept range header
+		/* At the moment we only support single ranges.
+		* Multiple ranges requires some more work to ensure it works correctly
+		* and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
+		*
+		* Multirange support annouces itself with:
+		* header('Accept-Ranges: bytes');
+		*
+		* Multirange content must be sent with multipart/byteranges mediatype,
+		* (mediatype = mimetype)
+		* as well as a boundry header to indicate the various chunks of data.
+		*/
+		header("Accept-Ranges: 0-$length");
+		// header('Accept-Ranges: bytes');
+		// multipart/byteranges
+		// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
+		if (isset($_SERVER['HTTP_RANGE']))
+		{
+			$c_start = $start;
+			$c_end	 = $end;
+			// Extract the range string
+			list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
+			// Make sure the client hasn't sent us a multibyte range
+			if (strpos($range, ',') !== false)
+			{
+				// (?) Shoud this be issued here, or should the first
+				// range be used? Or should the header be ignored and
+				// we output the whole content?
+				header('HTTP/1.1 416 Requested Range Not Satisfiable');
+				header("Content-Range: bytes $start-$end/$size");
+				// (?) Echo some info to the client?
+				exit;
+			}
+			// If the range starts with an '-' we start from the beginning
+			// If not, we forward the file pointer
+			// And make sure to get the end byte if spesified
+			if ($range{0} == '-')
+			{
+				// The n-number of the last bytes is requested
+				$c_start = $size - substr($range, 1);
+			}
+			else
+			{
+				$range	= explode('-', $range);
+				$c_start = $range[0];
+				$c_end	 = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
+			}
+			/* Check the range and make sure it's treated according to the specs.
+			* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+			*/
+			// End bytes can not be larger than $end.
+			$c_end = ($c_end > $end) ? $end : $c_end;
+			// Validate the requested range and return an error if it's not correct.
+			if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size)
+			{
+				header('HTTP/1.1 416 Requested Range Not Satisfiable');
+				header("Content-Range: bytes $start-$end/$size");
+				// (?) Echo some info to the client?
+				exit;
+			}
+			$start	= $c_start;
+			$end	= $c_end;
+			$length = $end - $start + 1; // Calculate new content length
+			fseek($fp, $start);
+			header('HTTP/1.1 206 Partial Content');
+			// Notify the client the byte range we'll be outputting
+			header("Content-Range: bytes $start-$end/$size");
+		}
+		header("Content-Length: $length");
+		header("Content-disposition: inline; filename=\"".$filename."\"\n");
+		header("Content-Type: ".$type."\n");
+
+		// Start buffered download
+		$buffer = 1024 * 8;
+		while(!feof($fp) && ($p = ftell($fp)) <= $end)
+		{
+			if ($p + $buffer > $end)
+			{
+				// In case we're only outputtin a chunk, make sure we don't
+				// read past the length
+				$buffer = $end - $p + 1;
+			}
+			set_time_limit(0); // Reset time limit for big files
+			echo fread($fp, $buffer);
+			flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
+		}
+
+		fclose($fp);
+	}
+
+}
-- 
1.8.4


More information about the Filebin-general mailing list