| | |
| | /** |
| | - * Isolate the file name being downloaded. |
| | + * Normalize the file name being downloaded. |
| | * |
| | - * @param array $fileinfo The result from calling pathinfo. |
| | + * @param string $path The path to the file. |
| | * |
| | * @return string The normalized file name. |
| | */ |
| | - function normalize_filename( $fileinfo ) { |
| | + function normalize_filename( $path ) { |
| | + $fileinfo = pathinfo( $path ); |
| | $basename = $fileinfo[ 'basename' ]; |
| | |
 |
| | |
| | /** |
| | - * Determine the content type based on the file name extension, rather |
| | - * than the file contents. This could be inaccurate, but we'll trust that |
| | - * the website administrator is posting files whose content reflects the |
| | - * file name extension. |
| | - * <p> |
| | - * If the file name extension is not known, the content type will force |
| | - * the download (to prevent the browser from trying to play the content |
| | - * directly). |
| | + * Downloads a file, allowing for resuming partial downloads. |
| | * |
| | - * @param array $fileinfo The result from calling pathinfo. |
| | + * @param string $path Fully qualified path of a file to download. |
| | * |
| | - * @return string The IANA-defined Media Type for the file name extension. |
| | + * @return bool True if the file was transferred. |
| | */ |
| | - function get_content_type( $fileinfo ) { |
| | - $extension = strtolower( $fileinfo[ 'extension' ] ); |
| | + function transmit( $path, $seek_start, $size ) { |
| | + if( ob_get_level() == 0 ) { |
| | + ob_start(); |
| | + } |
| | |
| | - switch( $extension ) { |
| | - case 'app': $ctype='application/octet-stream'; break; |
| | - case 'bin': $ctype='application/octet-stream'; break; |
| | - case 'exe': $ctype='application/octet-stream'; break; |
| | - case 'jar': $ctype='application/octet-stream'; break; |
| | - case 'zip': $ctype='application/zip'; break; |
| | - case 'avi': $ctype='video/msvideo'; break; |
| | - case 'mp3': $ctype='audio/mpeg'; break; |
| | - case 'mpg': $ctype='video/mpeg'; break; |
| | - case 'mpv': $ctype='video/mpv'; break; |
| | - case 'webm': $ctype='video/webm'; break; |
| | - default: $ctype='application/force-download'; break; |
| | + // If the file doesn't exist, don't count it as a download. |
| | + $bytes_sent = -1; |
| | + |
| | + // Open the file to be downloaded. |
| | + $fp = @fopen( $path, 'rb' ); |
| | + |
| | + if( $fp !== false ) { |
| | + @fseek( $fp, $seek_start ); |
| | + |
| | + $aborted = false; |
| | + $bytes_sent = $seek_start; |
| | + $chunk_size = 1024 * 16; |
| | + |
| | + while( !feof( $fp ) && !$aborted ) { |
| | + print( @fread( $fp, $chunk_size ) ); |
| | + $bytes_sent += $chunk_size; |
| | + |
| | + if( ob_get_level() > 0 ) { |
| | + ob_flush(); |
| | + } |
| | + |
| | + flush(); |
| | + |
| | + $aborted = connection_aborted() || connection_status() != 0; |
| | + } |
| | + |
| | + if( ob_get_level() > 0 ) { |
| | + ob_end_flush(); |
| | + } |
| | + |
| | + fclose( $fp ); |
| | } |
| | |
| | - return $ctype; |
| | + // Download succeeded if the total bytes matches or exceeds the file size. |
| | + return $bytes_sent >= $size; |
| | } |
| | |
| | /** |
| | * Downloads a file, allowing for resuming partial downloads. |
| | * |
| | * @param string $path Fully qualified path of a file to download. |
| | * |
| | - * @return bool True if the download succeeded. |
| | + * @return bool True if the file was transferred. |
| | */ |
| | function download( $path ) { |
| | // Don't cache the file stats result. |
| | clearstatcache(); |
| | |
| | $size = @filesize( $path ); |
| | $size = $size === false || empty( $size ) ? 0 : $size; |
| | - $fileinfo = pathinfo( $path ); |
| | - $filename = normalize_filename( $fileinfo ); |
| | - $content_type = get_content_type( $fileinfo ); |
| | + $filename = normalize_filename( $path ); |
| | + $content_type = mime_content_type( $filename ); |
| | $range = "0-$size"; |
| | |
 |
| | : max( abs( $seek_start + 0 ), 0 ); |
| | |
| | + header_remove( 'x-powered-by' ); |
| | header( 'Pragma: public' ); |
| | header( 'Expires: -1' ); |
| | header( 'Cache-Control: public, must-revalidate, post-check=0, pre-check=0' ); |
| | + header( 'Cache-Control: private', false ); |
| | header( "Content-Disposition: attachment; filename=\"$filename\"" ); |
| | + header( 'Content-Transfer-Encoding: binary' ); |
| | |
| | $content_length = $size; |
 |
| | header( "Content-Length: $content_length" ); |
| | header( "Content-Type: $content_type" ); |
| | - |
| | - // If the file doesn't exist, don't count it as a download. |
| | - $bytes_sent = -1; |
| | - |
| | - // Open the file to be downloaded. |
| | - $fp = @fopen( $path, 'rb' ); |
| | - |
| | - if( $fp !== false ) { |
| | - @fseek( $fp, $seek_start ); |
| | - |
| | - $aborted = false; |
| | - $bytes_sent = $seek_start; |
| | - $chunk_size = 1024 * 16; |
| | - |
| | - while( !feof( $fp ) && !$aborted ) { |
| | - print( @fread( $fp, $chunk_size ) ); |
| | - $bytes_sent += $chunk_size; |
| | - |
| | - if( ob_get_level() > 0 ) { |
| | - ob_flush(); |
| | - } |
| | - |
| | - flush(); |
| | - |
| | - $aborted = connection_aborted() || connection_status() != 0; |
| | - } |
| | - |
| | - if( ob_get_level() > 0 ) { |
| | - ob_end_flush(); |
| | - } |
| | - |
| | - fclose( $fp ); |
| | - } |
| | |
| | - // Download succeeded if the total bytes matches or exceeds the file size. |
| | - return $bytes_sent >= $size; |
| | + // Respond to HTTP HEAD requests. |
| | + return $_SERVER['REQUEST_METHOD'] === 'HEAD' |
| | + ? false |
| | + : transmit( $path, $seek_start, $size ); |
| | } |
| | |