<?php


/**
 * Check the safety of a filename's extension(s).
 *
 * Returns true if a file appears to have exactly one safe extension,
 * no unsafe extensions, and (optionally) at most one compression extension.
 * Otherwise, returns false.
 *
 * Note this only checks the *name* of the file, not its *content*.
 *
 * @author support@NearlyFreeSpeech.NET
 *
 * @param  string $i_strFile The filename to check.
 * @return bool              True if the filename is believed safe,
 *                           otherwise false.
 */
function IsFileExtensionSafe( string $i_strFile ) : bool {

	##  Choose or create a whitelist suitable for the types of content
	##  your site will be working with.
	$rWhitelist = [
		'gif', 'jpeg', 'jpg', 'png',                           ##  Images
		// 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx',        ##  MS Office
		// 'pdf', 'rtf', 'txt'                                 ##  Documents
		// 'au', 'flac', 'mp3', 'vorbis', 'wav',               ##  Audio
		// 'avi', 'mp4', 'mpeg', 'mpg', 'ogg', 'qt',           ##  Video
		// 'arj', 'lha' 'rar', 'tar', 'tbz', 'tgz', 'zip',
	];

	##  Uncomment these to allow one to be combined with a valid extension.
	$rCompressList = [
		// '7z', 'bz', 'gz', 'zip'
	];

	##  These are the most common types of executable files at NFSN.
	$rBlacklist = [
		'cgi', 'php', 'pl', 'py', 'rb',   ##  Default script extensions at NFSN
		'css', 'html', 'js',              ##  Generally bad ideas to upload
	];

	$bOneValid    = false;
	$bOneCompress = false;

	$rExts = explode( '.', strtolower( basename( $i_strFile ) ) );
	array_shift( $rExts );  ##  First component is definitely not an extension.
	foreach ( $rExts as $strExt ) {

		if ( in_array( $strExt, $rBlacklist ) )
			return false;      ## One blacklisted extension = automatic fail

		if ( in_array( $strExt, $rWhitelist ) ) {
			if ( $bOneValid )
				return false;  ## Multiple conficting (but valid) extensions?!
			$bOneValid = true;
			continue;
		}

		if ( ! empty( $rCompressList )
			 && in_array( $strExt, $rCompressList ) ) {
			if ( $bOneCompress )
				return false;  ##  Multiple conflicting compression extensions
			$bOneCompress = true;
			// continue; # Implicit.
		}

	}

	return $bOneValid;

}
