Added support for stream-like downloads of existing files to force_download()

Based on code/ideas from PR #365, #1254
diff --git a/system/helpers/download_helper.php b/system/helpers/download_helper.php
index 8fe66e2..31652d5 100644
--- a/system/helpers/download_helper.php
+++ b/system/helpers/download_helper.php
@@ -56,6 +56,23 @@
 		{
 			return FALSE;
 		}
+		elseif ($data === NULL)
+		{
+			if (@is_file($filename) && @file_exists($filename) && ($filesize = @filesize($filename)) !== FALSE)
+			{
+				$filepath = $filename;
+				$filename = explode('/', str_replace(DIRECTORY_SEPARATOR, '/', $filename));
+				$filename = end($filename);
+			}
+			else
+			{
+				return FALSE;
+			}
+		}
+		else
+		{
+			$filesize = strlen($data);
+		}
 
 		// Set the default MIME type to send
 		$mime = 'application/octet-stream';
@@ -95,8 +112,13 @@
 			$filename = implode('.', $x);
 		}
 
+		if ($data === NULL && ($fp = @fopen($filepath, 'rb')) === FALSE)
+		{
+			return FALSE;
+		}
+
 		// Clean output buffer
-		if (ob_get_level() !== 0)
+		if (ob_get_level() !== 0 && @ob_end_clean() === FALSE)
 		{
 			ob_clean();
 		}
@@ -106,7 +128,7 @@
 		header('Content-Disposition: attachment; filename="'.$filename.'"');
 		header('Expires: 0');
 		header('Content-Transfer-Encoding: binary');
-		header('Content-Length: '.strlen($data));
+		header('Content-Length: '.$filesize);
 
 		// Internet Explorer-specific headers
 		if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== FALSE)
@@ -116,7 +138,20 @@
 
 		header('Pragma: no-cache');
 
-		exit($data);
+		// If we have raw data - just dump it
+		if ($data !== NULL)
+		{
+			exit($data);
+		}
+
+		// Flush 1MB chunks of data
+		while ( ! feof($fp) && ($data = fread($fp, 1048576)) !== FALSE)
+		{
+			echo $data;
+		}
+
+		fclose($fp);
+		exit;
 	}
 }
 
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 6047f1c..e523c4c 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -77,8 +77,10 @@
    -  :doc:`Inflector Helper <helpers/inflector_helper>` changes include:
 	 - Changed :php:func:`humanize()` to allow passing an input separator as its second parameter.
 	 - Refactored :php:func:`plural()` and :php:func:`singular()` to avoid double pluralization and support more words.
-   -  Added an optional third parameter to ``force_download()`` that enables/disables sending the actual file MIME type in the Content-Type header (disabled by default).
-   -  Added a work-around in ``force_download()`` for a bug Android <= 2.1, where the filename extension needs to be in uppercase.
+   -  :doc:`Download Helper <helpers/download_helper>` changes include:
+	 - Added an optional third parameter to :php:func:`force_download()` that enables/disables sending the actual file MIME type in the Content-Type header (disabled by default).
+	 - Added a work-around in :php:func:`force_download()` for a bug Android <= 2.1, where the filename extension needs to be in uppercase.
+	 - Added support for reading from an existing file path by passing NULL as the second parameter to :php:func:`force_download()` (useful for large files and/or safely transmitting binary data).
    -  :doc:`Form Helper <helpers/form_helper>` changes include:
 	 - :php:func:`form_dropdown()` will now also take an array for unity with other form helpers.
 	 - :php:func:`form_prep()`'s second argument now only accepts a boolean value, which determines whether the value is escaped for a <textarea> or a regular <input> element.
diff --git a/user_guide_src/source/helpers/download_helper.rst b/user_guide_src/source/helpers/download_helper.rst
index 1e9ec21..860c568 100644
--- a/user_guide_src/source/helpers/download_helper.rst
+++ b/user_guide_src/source/helpers/download_helper.rst
@@ -21,7 +21,7 @@
 .. php:function:: force_download($filename = '', $data = '', $set_mime = FALSE)
 
 	:param	string	$filename: Filename
-	:param	string	$data: File contents
+	:param	mixed	$data: File contents
 	:param	bool	$set_mime: Whether to try to send the actual MIME type
 	:returns:	void
 
@@ -30,6 +30,9 @@
 you want the downloaded file to be named**, the second parameter is the
 file data.
 
+If you set the second parameter to NULL and ``$filename`` is an existing, readable
+file path, then its content will be read instead.
+
 If you set the third parameter to boolean TRUE, then the actual file MIME type
 (based on the filename extension) will be sent, so that if your browser has a
 handler for that type - it can use it.
@@ -41,8 +44,7 @@
 	force_download($name, $data);
 
 If you want to download an existing file from your server you'll need to
-read the file into a string::
+do the following::
 
-	$data = file_get_contents('/path/to/photo.jpg'); // Read the file's contents
-	$name = 'myphoto.jpg';
-	force_download($name, $data);
\ No newline at end of file
+	// Contents of photo.jpg will be automatically read
+	force_download('/path/to/photo.jpg', NULL);
\ No newline at end of file