Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.com.git
downloads/counter.php
set_time_limit( 0 );
+ ignore_user_abort( true );
- while( ob_get_level() > 0 ) {
- ob_end_flush();
+ $level = ob_get_level();
+ $cleared = false;
+
+ while( $level > 0 && !$cleared ) {
+ ob_end_clean();
+
+ $newLevel = ob_get_level();
+
+ if( $newLevel === $level ) {
+ $cleared = true;
+ }
+ else {
+ $level = $newLevel;
+ }
}
if( session_id() === "" ) {
session_start();
}
- ignore_user_abort( true );
+ class DownloadManager {
+ private $filename;
+ private $expiry;
- $filename = get_sanitized_filename();
- $valid = !empty( $filename );
- $expiry = 24 * 60 * 60;
+ public function __construct( $filename, $expiry ) {
+ $this->filename = $filename;
+ $this->expiry = $expiry;
+ }
- if( $valid && download( $filename ) && token_expired( $expiry ) ) {
- increment_count( "$filename-count.txt" );
- }
+ public function process() {
+ $result = false;
- /**
- * Retrieve the file name being downloaded from the HTTP GET request.
- *
- * @return string The sanitized file name (without path information).
- */
- function get_sanitized_filename() {
- $filepath = isset( $_GET[ 'filename' ] ) ? $_GET[ 'filename' ] : '';
- $fileinfo = pathinfo( $filepath );
- $basename = $fileinfo[ 'basename' ];
+ if( $this->filename !== '' ) {
+ if( $this->tokenExpired() ) {
+ $this->incrementCount();
+ }
- if( isset( $_SERVER[ 'HTTP_USER_AGENT' ] ) ) {
- $periods = substr_count( $basename, '.' );
+ $result = $this->download();
+ }
- $basename = strstr( $_SERVER[ 'HTTP_USER_AGENT' ], 'MSIE' )
- ? mb_ereg_replace( '/\./', '%2e', $basename, $periods - 1 )
- : $basename;
+ return $result;
}
-
- $basename = mb_ereg_replace( '/\s+/', '', $basename );
- $basename = mb_ereg_replace( '([^\w\d\-_~,;\[\]\(\).])', '', $basename );
- return $basename;
- }
+ /**
+ * Downloads a file, allowing for resuming partial downloads.
+ *
+ * @return bool True if the file was transferred.
+ */
+ private function download() {
+ $result = false;
+ $method = isset( $_SERVER[ 'REQUEST_METHOD' ] )
+ ? $_SERVER[ 'REQUEST_METHOD' ]
+ : 'GET';
+ $isHead = $method === 'HEAD';
+ $contentType = mime_content_type( $this->filename );
- /**
- * Answers whether the user's download token has expired.
- *
- * @param int $lifetime Number of seconds before expiring the token.
- *
- * @return bool True indicates the token has expired (or was not set).
- */
- function token_expired( $lifetime ) {
- $TOKEN_NAME = 'LAST_DOWNLOAD';
- $now = time();
- $expired = !isset( $_SESSION[ $TOKEN_NAME ] );
+ clearstatcache();
- if( !$expired && ( $now - $_SESSION[ $TOKEN_NAME ] > $lifetime ) ) {
- $expired = true;
- $_SESSION = array();
+ $fileSize = @filesize( $this->filename );
+ $size = $fileSize === false || empty( $fileSize ) ? 0 : $fileSize;
- session_destroy();
- }
+ if( !$isHead ) {
+ $rangeData = $this->parseRange( $size );
+ $start = $rangeData[ 0 ];
+ $length = $rangeData[ 1 ];
- $_SESSION[ $TOKEN_NAME ] = $now;
+ if( $length > 0 ) {
+ header_remove( 'x-powered-by' );
+ header( 'Expires: 0' );
+ header(
+ 'Cache-Control: public, must-revalidate, post-check=0, pre-check=0'
+ );
+ header( 'Cache-Control: private', false );
+ header(
+ 'Content-Disposition: attachment; filename="' .
+ $this->filename . '"'
+ );
+ header( 'Accept-Ranges: bytes' );
+ header( "Content-Length: $length" );
+ header( "Content-Type: $contentType" );
- $TOKEN_CREATE = 'CREATED';
+ $result = $this->transmit( $start, $size );
+ }
+ }
- if( !isset( $_SESSION[ $TOKEN_CREATE ] ) ) {
- $_SESSION[ $TOKEN_CREATE ] = $now;
- }
- else if( $now - $_SESSION[ $TOKEN_CREATE ] > $lifetime ) {
- session_regenerate_id( true );
- $_SESSION[ $TOKEN_CREATE ] = $now;
+ return $result;
}
-
- return $expired;
- }
-
- /**
- * Downloads a file, allowing for resuming partial downloads.
- *
- * @param string $filename File to download, must be in script directory.
- *
- * @return bool True if the file was transferred.
- */
- function download( $filename ) {
- clearstatcache();
- $size = @filesize( $filename );
- $size = $size === false || empty( $size ) ? 0 : $size;
- $content_type = mime_content_type( $filename );
- list( $seek_start, $content_length ) = parse_range( $size );
+ /**
+ * Parses the HTTP range request header, provided one was sent by the
+ * client. This provides download resume functionality.
+ *
+ * @param int $size The total file size (as stored on disk).
+ *
+ * @return array The starting offset for resuming the download, or 0 to
+ * download the entire file (i.e., no offset could be parsed); also the
+ * number of bytes to be transferred.
+ */
+ private function parseRange( $size ) {
+ $start = 0;
+ $length = $size;
- header_remove( 'x-powered-by' );
- header( 'Expires: 0' );
- header( 'Cache-Control: public, must-revalidate, post-check=0, pre-check=0' );
- header( 'Cache-Control: private', false );
- header( "Content-Disposition: attachment; filename=\"$filename\"" );
- header( 'Accept-Ranges: bytes' );
- header( "Content-Length: $content_length" );
- header( "Content-Type: $content_type" );
+ if( isset( $_SERVER[ 'HTTP_RANGE' ] ) ) {
+ $format = '/^bytes=(\d*)-(\d*)(?:,\d*-\d*)*$/';
+ $range = $_SERVER[ 'HTTP_RANGE' ];
+ $match = preg_match( $format, $range, $matches );
- $method = isset( $_SERVER[ 'REQUEST_METHOD' ] )
- ? $_SERVER[ 'REQUEST_METHOD' ]
- : 'GET';
+ if( $match ) {
+ $start = isset( $matches[ 1 ] ) ? (int)$matches[ 1 ] : 0;
+ $end = !empty( $matches[ 2 ] ) ? (int)$matches[ 2 ] : $size - 1;
+ $length = $end - $start + 1;
- return $method === 'HEAD'
- ? false
- : transmit( $filename, $seek_start, $size );
- }
+ header( 'HTTP/1.1 206 Partial Content' );
+ header( "Content-Range: bytes $start-$end/$size" );
+ }
+ else {
+ header( 'HTTP/1.1 416 Requested Range Not Satisfiable' );
+ header( "Content-Range: bytes */$size" );
- /**
- * Parses the HTTP range request header, provided one was sent by the
- * client. This provides download resume functionality.
- *
- * @param int $size The total file size (as stored on disk).
- *
- * @return array The starting offset for resuming the download, or 0 to
- * download the entire file (i.e., no offset could be parsed); also the
- * number of bytes to be transferred.
- */
- function parse_range( $size ) {
- $seek_start = 0;
- $content_length = $size;
+ $length = 0;
+ }
+ }
- if( isset( $_SERVER[ 'HTTP_RANGE' ] ) ) {
- $range_format = '/^bytes=(\d*)-(\d*)(?:,\d*-\d*)*$/';
- $request_range = $_SERVER[ 'HTTP_RANGE' ];
+ return array(
+ $start,
+ $length
+ );
+ }
- if( !preg_match( $range_format, $request_range, $matches ) ) {
- header( 'HTTP/1.1 416 Requested Range Not Satisfiable' );
- header( "Content-Range: bytes */$size" );
+ /**
+ * Transmits a file from the server to the client.
+ *
+ * @param int $seekStart Offset into file to start downloading.
+ * @param int $size Total size of the file.
+ *
+ * @return bool True if the file was transferred.
+ */
+ private function transmit( $seekStart, $size ) {
+ $result = false;
+ $bytesSent = -1;
+ $fp = @fopen( $this->filename, 'rb' );
- exit;
- }
+ if( $fp !== false ) {
+ $aborted = false;
+ $bytesSent = $seekStart;
+ $chunkSize = 1024 * 16;
- $seek_start = isset( $matches[ 1 ] ) ? $matches[ 1 ] + 0 : 0;
- $seek_end = !empty( $matches[ 2 ] ) ? $matches[ 2 ] + 0 : $size - 1;
- $range_bytes = $seek_start . '-' . $seek_end . '/' . $size;
- $content_length = $seek_end - $seek_start + 1;
+ @fseek( $fp, $seekStart );
- header( 'HTTP/1.1 206 Partial Content' );
- header( "Content-Range: bytes $range_bytes" );
- }
+ while( !feof( $fp ) && !$aborted ) {
+ print( @fread( $fp, $chunkSize ) );
- return array( $seek_start, $content_length );
- }
+ $bytesSent += $chunkSize;
+ $aborted = connection_aborted() || connection_status() !== 0;
- /**
- * Transmits a file from the server to the client.
- *
- * @param string $filename File to download, must be this script directory.
- * @param int $seek_start Offset into file to start downloading.
- * @param int $size Total size of the file.
- *
- * @return bool True if the file was transferred.
- */
- function transmit( $filename, $seek_start, $size ) {
- if( ob_get_level() == 0 ) {
- ob_start();
- }
+ flush();
+ }
- $bytes_sent = -1;
- $fp = @fopen( $filename, 'rb' );
+ fclose( $fp );
- if( $fp !== false ) {
- @fseek( $fp, $seek_start );
+ $result = $bytesSent >= $size;
+ }
- $aborted = false;
- $bytes_sent = $seek_start;
- $chunk_size = 1024 * 16;
+ return $result;
+ }
- while( !feof( $fp ) && !$aborted ) {
- print( @fread( $fp, $chunk_size ) );
+ /**
+ * Answers whether the user's download token has expired.
+ *
+ * @return bool True indicates the token has expired (or was not set).
+ */
+ private function tokenExpired() {
+ $tokenName = 'LAST_DOWNLOAD';
+ $tokenCreate = 'CREATED';
+ $now = time();
+ $expired = !isset( $_SESSION[ $tokenName ] );
- $bytes_sent += $chunk_size;
+ if( !$expired && $now - $_SESSION[ $tokenName ] > $this->expiry ) {
+ $expired = true;
+ $_SESSION = array();
- if( ob_get_level() > 0 ) {
- ob_flush();
- }
+ session_destroy();
+ }
- flush();
+ $_SESSION[ $tokenName ] = $now;
- $aborted = connection_aborted() || connection_status() != 0;
+ if( !isset( $_SESSION[ $tokenCreate ] ) ) {
+ $_SESSION[ $tokenCreate ] = $now;
}
+ else {
+ $elapsed = $now - $_SESSION[ $tokenCreate ];
- if( ob_get_level() > 0 ) {
- ob_end_flush();
+ if( $elapsed > $this->expiry ) {
+ session_regenerate_id( true );
+
+ $_SESSION[ $tokenCreate ] = $now;
+ }
}
- fclose( $fp );
+ return $expired;
}
- return $bytes_sent >= $size;
- }
+ /**
+ * Increments the number in a file using an exclusive lock. The file
+ * is set to an initial value set to 0 if it doesn't exist.
+ */
+ private function incrementCount() {
+ $result = false;
+ $filename = $this->filename . '-count.txt';
+ $lockDir = $filename . '.lock';
+ $locked = $this->lockOpen( $lockDir );
- /**
- * Increments the number in a file using an exclusive lock. The file
- * is set to an initial value set to 0 if it doesn't exist.
- *
- * @param string $filename The file containing a number to increment.
- */
- function increment_count( $filename ) {
- try {
- lock_open( $filename );
+ if( $locked ) {
+ $count = (int)@file_get_contents( $filename ) + 1;
- $count = @file_get_contents( $filename ) + 1;
+ @file_put_contents( $filename, $count );
+ @rmdir( $lockDir );
- file_put_contents( $filename, $count );
- }
- finally {
- lock_close( $filename );
+ $result = true;
+ }
+
+ return $result;
}
- }
- /**
- * Acquires a lock for a particular file. Callers would be prudent to
- * call this function from within a try/finally block and close the lock
- * in the finally section. The amount of time between opening and closing
- * the lock must be minimal because parallel processes will be waiting on
- * the lock's release.
- *
- * @param string $filename The name of file to lock.
- *
- * @return bool True if the lock was obtained, false upon excessive attempts.
- */
- function lock_open( $filename ) {
- $lockdir = create_lock_filename( $filename );
- $iterations = 0;
+ /**
+ * Acquires a lock for a particular file. Callers would be prudent to
+ * call this function from within a try/finally block and close the lock
+ * in the finally section. The amount of time between opening and closing
+ * the lock must be minimal because parallel processes will be waiting on
+ * the lock's release.
+ *
+ * @param string $lockDir The name of directory to lock.
+ *
+ * @return bool True if the lock was obtained, false upon excessive
+ * attempts.
+ */
+ private function lockOpen( $lockDir ) {
+ $result = false;
+ $iterations = 0;
- do {
- if( @mkdir( $lockdir, 0777 ) ) {
- $iterations = 0;
- }
- else {
- $iterations++;
- $lifetime = time() - filemtime( $lockdir );
+ while( $iterations < 10 && !$result ) {
+ $result = @mkdir( $lockDir, 0777 );
- if( $lifetime > 10 ) {
- @rmdir( $lockdir );
- }
- else {
- usleep( rand( 1000, 10000 ) );
+ if( !$result ) {
+ $iterations++;
+ $lifetime = time() - (int)@filemtime( $lockDir );
+
+ if( $lifetime > 10 ) {
+ @rmdir( $lockDir );
+ }
+ else {
+ usleep( rand( 1000, 10000 ) );
+ }
}
}
- }
- while( $iterations > 0 && $iterations < 10 );
- return $iterations == 0;
+ return $result;
+ }
}
/**
- * Releases the lock on a particular file.
+ * Retrieve the file name being downloaded from the HTTP GET request.
*
- * @param string $filename The name of file that was locked.
+ * @return string The sanitized file name (without path information).
*/
- function lock_close( $filename ) {
- @rmdir( create_lock_filename( $filename ) );
- }
+ function getSanitizedFilename() {
+ $filepath = isset( $_GET[ 'filename' ] ) ? $_GET[ 'filename' ] : '';
+ $fileinfo = pathinfo( $filepath );
+ $basename = $fileinfo[ 'basename' ];
+ $noSpaces = preg_replace( '/\s+/', '', $basename );
- /**
- * Creates a uniquely named lock directory name.
- *
- * @param string $filename The name of the file under contention.
- *
- * @return string A unique lock file reference for the given file name.
- */
- function create_lock_filename( $filename ) {
- return $filename . '.lock';
+ return preg_replace( '/[^\w\d\-_~,;\[\]\(\).]/', '', $noSpaces );
}
+
+ $filename = getSanitizedFilename();
+ $expiry = 24 * 60 * 60;
+ $manager = new DownloadManager( $filename, $expiry );
+
+ $manager->process();
?>

Reinstates comments

Author Dave Jarvis <email>
Date 2026-03-03 12:18:59 GMT-0800
Commit cdeb7fb4fd4c3fac967a9d6ffaf6ec18d529563e
Parent 2f1f12d
Delta 217 lines added, 205 lines removed, 12-line increase