# This should be handled by the XSendFile module, but a bug in 0.11.1 prevented it from being set properly. EnableSendfile on XSendFile on # Absolute path to where your download files are stored. No trailing slash! XSendFilePath "C:\inetpub\apacheroot\mysite\download_files" */ function join_paths($paths) { if (!is_array($paths)) $paths = func_get_args(); foreach ($paths as $i => $part) { $part = trim(preg_replace('|[\\\\/]+|', DIRECTORY_SEPARATOR, trim($part)) , DIRECTORY_SEPARATOR); if (!strlen($part)) unset($paths[$i]); else $paths[$i] = $part; } return join(DIRECTORY_SEPARATOR, $paths); } function can_haz_xsendfile() { // This will return false if PHP is not loaded as a module (i.e. uses cgi) return in_array('mod_xsendfile', apache_get_modules()); } function send_download_package_file($base_path, $file_path) { $realpath = join_paths(DOWNLOAD_PACKAGE_BASE_ROOT, $base_path, $file_path); if (file_exists($realpath)) { // Fetching File $mtime = ($mtime = filemtime($realpath)) ? $mtime : gmtime(); $size = intval(sprintf("%u", filesize($realpath))); header("Content-type: application/force-download"); header('Content-Type: application/octet-stream'); if (strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != false) { header("Content-Disposition: attachment; filename=" . urlencode(basename($file_path)) . '; modification-date="' . date('r', $mtime) . '";'); } else { header("Content-Disposition: attachment; filename=\"" . basename($file_path) . '"; modification-date="' . date('r', $mtime) . '";'); } if (can_haz_xsendfile()) { // Sending file via mod_xsendfile header("X-Sendfile: " . join_paths($base_path, $file_path)); } else { // Sending file directly via script if (intval($size + 1) > return_bytes(ini_get('memory_limit')) && intval($size * 1.5) <= 1073741824) { //Not higher than 1GB // Setting memory limit ini_set('memory_limit', intval($size * 1.5)); } @apache_setenv('no-gzip', 1); @ini_set('zlib.output_compression', 0); header("Content-Length: " . $size); // Set the time limit based on an average D/L speed of 50kb/sec set_time_limit(min(7200, // No more than 120 minutes (this is really bad, but...) ($size > 0) ? intval($size / 51200) + 60 // 1 minute more than what it should take to D/L at 50kb/sec : 1 // Minimum of 1 second in case size is found to be 0 )); $chunksize = 1 * (1024 * 1024); // how many megabytes to read at a time if ($size > $chunksize) { // Chunking file for download $handle = fopen($realpath, 'rb'); $buffer = ''; while (!feof($handle)) { $buffer = fread($handle, $chunksize); echo $buffer; ob_flush(); flush(); } fclose($handle); } else { // Streaming whole file for download readfile($realpath); } } return true; } else { // File not found! Throw error here... } return false; } // Absolute path, same as what you set for the XSendFilePath directive define('DOWNLOAD_PACKAGE_BASE_ROOT', 'C:\inetpub\apacheroot\mysite\download_files'); // Any intermediate path between XSendFilePath and your file. It should have neither a leading nor trailing slash define('DOWNLOAD_PACKAGE_DIR', 'pdfs'); if (send_download_package_file(DOWNLOAD_PACKAGE_DIR, 'some_big_file.pdf')) { // Exit successfully. We could just let the script exit // normally at the bottom of the page, but then blank lines // after the close of the script code would potentially cause // problems after the file download. exit; } // else, raise error... ?>