2019-03-29 19:13:32 -05:00
< ? php
/*
* e107 website system
*
* Copyright ( C ) 2008 - 2013 e107 Inc ( e107 . org )
* Released under the terms and conditions of the
* GNU General Public License ( http :// www . gnu . org / licenses / gpl . txt )
*
* File Upload Handler
*
* $Source : / cvs_backup / e107_0 . 8 / e107_handlers / upload_handler . php , v $
* $Revision $
* $Date $
* $Author $
*/
/**
* File upload handler
*
* @ package e107
* @ subpackage e107_handlers
* @ version $Id $ ;
*
* @ todo - option to restrict by total size irrespective of number of uploads
*/
if ( ! defined ( 'e107_INIT' ))
{
exit ;
}
e107 :: includeLan ( e_LANGUAGEDIR . e_LANGUAGE . '/lan_upload_handler.php' );
//define("UH_DEBUG",TRUE);
//FIXME need another name
// define('e_UPLOAD_TEMP_DIR', e_MEDIA.'temp/');
define ( 'e_UPLOAD_TEMP_DIR' , e_TEMP ); // Temporary directory - used if PHP's OPEN_BASEDIR active
define ( 'e_READ_FILETYPES' , 'filetypes.xml' ); // Upload permissions
define ( 'e_SAVE_FILETYPES' , 'filetypes_.xml' );
/**
* File upload handler - this is the preferred interface for new code
*
* Routine processes the array of uploaded files according to both specific options set by the caller , and
* system options configured by the main admin .
*
* function process_uploaded_files ( $uploaddir , $fileinfo = FALSE , $options = array ())
* Parameters :
* @ param string $uploaddir - target directory ( checked that it exists , but path not otherwise changed )
*
* @ param string $fileinfo - determines any special handling of file name ( combines previous $fileinfo and $avatar parameters ) :
* FALSE - default option ; no processing
* " attachment+extra_text " - indicates an attachment ( related to forum post or PM ), and specifies some optional text which is
* incorporated into the final file name ( the original $fileinfo parameter ) .
* " prefix+extra_text " - indicates an attachment or file , and specifies some optional text which is prefixed to the file name
* " unique "
* - if the proposed destination file doesn ' t exist , saved under given name
* - if the proposed destination file does exist , prepends time () to the file name to make it unique
* 'avatar'
* - indicates an avatar is being uploaded ( not used - options must be set elsewhere )
*
* @ param array $options - an array of supplementary options , all of which will be given appropriate defaults if not defined :
* 'filetypes' - name of file containing list of valid file types
* - Always looks in the admin directory
* - defaults to e_ADMIN . filetypes . xml , else e_ADMIN . admin_filetypes . php for admins ( if file exists ), otherwise e_ADMIN . filetypes . php for users .
* - FALSE disables this option ( which implies that 'extra_file_types' is used )
* 'file_mask' - comma - separated list of file types which if defined limits the allowed file types to those which are in both this list and the
* file specified by the 'filetypes' option . Enables restriction to , for example , image files .
* 'extra_file_types' - if is FALSE or undefined , rejects totally unknown file extensions ( even if in $options [ 'filetypes' ] file ) .
* if TRUE , accepts totally unknown file extensions which are in $options [ 'filetypes' ] file .
* otherwise specifies a comma - separated list of additional permitted file extensions
* 'final_chmod' - chmod () to be applied to uploaded files ( 0644 default ) ( This routine expects an integer value , so watch formatting / decoding - its normally
* specified in octal . Typically use intval ( $permissions , 8 ) to convert )
* 'max_upload_size' - maximum size of uploaded files in bytes , or as a string with a 'multiplier' letter ( e . g . 16 M ) at the end .
* - otherwise uses $pref [ 'upload_maxfilesize' ] if set
* - overriding limit of the smaller of 'post_max_size' and 'upload_max_size' if set in php . ini
* ( Note : other parts of E107 don ' t understand strings with a multiplier letter yet )
* 'file_array_name' - the name of the 'input' array - defaults to file_userfile [] - otherwise as set .
* 'max_file_count' - maximum number of files which can be uploaded - default is 'unlimited' if this is zero or not set .
* 'overwrite' - if TRUE , existing file of the same name is overwritten ; otherwise returns 'duplicate file' error ( default FALSE )
* 'save_to_db' - [ obsolete ] storage type - if set and TRUE , uploaded files were saved in the database ( rather than as flat files )
*
* @ return boolean | array
* Returns FALSE if the upload directory doesn ' t exist , or various other errors occurred which restrict the amount of meaningful information .
* Returns an array , with one set of entries per uploaded file , regardless of whether saved or
* discarded ( not all fields always present ) - $c is array index :
* $uploaded [ $c ][ 'name' ] - file name - as saved to disc
* $uploaded [ $c ][ 'rawname' ] - original file name , prior to any addition of identifiers etc ( useful for display purposes )
* $uploaded [ $c ][ 'type' ] - mime type ( if set - as sent by browser )
* $uploaded [ $c ][ 'size' ] - size in bytes ( should be zero if error )
* $uploaded [ $c ][ 'error' ] - numeric error code ( zero = OK )
* $uploaded [ $c ][ 'index' ] - if upload successful , the index position from the file_userfile [] array - usually numeric , but may be alphanumeric if coded
* $uploaded [ $c ][ 'message' ] - text of displayed message relating to file
* $uploaded [ $c ][ 'line' ] - only if an error occurred , has line number ( from __LINE__ )
* $uploaded [ $c ][ 'file' ] - only if an error occurred , has file name ( from __FILE__ )
*
* On exit , uploaded files should all have been removed from the temporary directory .
* No messages displayed - its caller ' s responsibility to handle errors and display info to
* user ( or can use handle_upload_messages () from this module )
*
* Details of uploaded files are in $_FILES [ 'file_userfile' ] ( or other array name as set ) on entry .
* Elements passed ( from PHP ) relating to each file :
* [ 'name' ] - the original name
* [ 'type' ] - mime type ( if provided - not checked by PHP )
* [ 'size' ] - file size in bytes
* [ 'tmp_name' ] - temporary file name on server
* [ 'error' ] - error code . 0 = 'good' . 1. . 4 main others , although up to 8 defined for later PHP versions
* Files stored in server ' s temporary directory , unless another set
*/
function process_uploaded_files ( $uploaddir , $fileinfo = FALSE , $options = NULL )
{
2020-12-22 09:36:02 -08:00
$admin_log = e107 :: getLog ();
2019-03-29 19:13:32 -05:00
$ul_temp_dir = '' ;
if ( ini_get ( 'open_basedir' ) != '' ) // Need to move file to intermediate directory before we can read its contents to check it.
{
$ul_temp_dir = e_UPLOAD_TEMP_DIR ;
}
if ( E107_DEBUG_LEVEL > 0 )
{
define ( " UH_DEBUG " , true );
}
else
{
define ( " UH_DEBUG " , false );
}
if ( UH_DEBUG )
{
2020-12-18 09:39:02 -08:00
e107 :: getLog () -> addEvent ( 10 , debug_backtrace (), " DEBUG " , " Upload Handler test " , " Process uploads to { $uploaddir } , fileinfo " . $fileinfo , FALSE , LOG_TO_ROLLING );
2019-03-29 19:13:32 -05:00
}
2020-12-18 09:39:02 -08:00
// $admin_log->addEvent(10,__FILE__."|".__FUNCTION__."@".__LINE__,"DEBUG","Upload Handler test","Intermediate directory: {$ul_temp_dir} ",FALSE,LOG_TO_ROLLING);
2019-03-29 19:13:32 -05:00
$overwrite = varset ( $options [ 'overwrite' ], FALSE );
$uploaddir = realpath ( $uploaddir ); // Mostly to get rid of the grot that might be passed in from legacy code. Also strips any trailing '/'
if ( ! is_dir ( $uploaddir ))
{
if ( UH_DEBUG )
{
2021-01-18 18:10:00 -08:00
// e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Invalid directory: ".$uploaddir, FALSE, FALSE);
2019-03-29 19:13:32 -05:00
}
return FALSE ; // Need a valid directory
}
if ( UH_DEBUG )
{
2021-01-18 18:10:00 -08:00
// e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Destination directory: ".$uploaddir, FALSE, FALSE);
2019-03-29 19:13:32 -05:00
}
$final_chmod = varset ( $options [ 'final_chmod' ], 0644 );
if ( isset ( $options [ 'file_array_name' ]))
{
$files = $_FILES [ $options [ 'file_array_name' ]];
}
else
{
$files = $_FILES [ 'file_userfile' ];
}
$max_file_count = varset ( $options [ 'max_file_count' ], 0 );
2021-01-18 18:10:00 -08:00
if ( empty ( $files ))
2019-03-29 19:13:32 -05:00
{
2021-01-18 18:10:00 -08:00
if ( deftrue ( 'UH_DEBUG' ))
2019-03-29 19:13:32 -05:00
{
2020-12-18 09:39:02 -08:00
e107 :: getLog () -> addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " No files uploaded " , FALSE , FALSE );
2019-03-29 19:13:32 -05:00
}
return FALSE ;
}
$uploaded = array ();
$max_upload_size = calc_max_upload_size ( varset ( $options [ 'max_upload_size' ], - 1 )); // Find overriding maximum upload size
$allowed_filetypes = get_filetypes ( varset ( $options [ 'file_mask' ], '' ), varset ( $options [ 'filetypes' ], '' ));
$max_upload_size = set_max_size ( $allowed_filetypes , $max_upload_size );
// That's the basics set up - we can start processing files now
2021-01-18 18:10:00 -08:00
if ( deftrue ( 'UH_DEBUG' ))
2019-03-29 19:13:32 -05:00
{
2021-01-18 18:10:00 -08:00
// e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Start individual files: ".count($files['name'])." Max upload: ".$max_upload_size, FALSE, FALSE);
2019-03-29 19:13:32 -05:00
}
$c = 0 ;
$tp = e107 :: getParser ();
$uploadfile = null ;
foreach ( $files [ 'name' ] as $key => $name )
{
2021-09-04 15:06:19 +02:00
$name = $tp -> filter ( $name , 'str' );
2019-03-29 19:13:32 -05:00
$first_error = FALSE ; // Clear error flag
if (( $name != '' ) || $files [ 'size' ][ $key ]) // Need this check for things like file manager which allow multiple possible uploads
{
$origname = $name ;
//$name = preg_replace("/[^a-z0-9._-]/", '', str_replace(' ', '_', str_replace('%20', '_', strtolower($name))));
// FIX handle non-latin file names
$name = preg_replace ( " /[^ \ w \ pL.-]/u " , '' , str_replace ( ' ' , '_' , str_replace ( '%20' , '_' , $tp -> ustrtolower ( $name ))));
$raw_name = $name ; // Save 'proper' file name - useful for display
$file_ext = trim ( strtolower ( substr ( strrchr ( $name , " . " ), 1 ))); // File extension - forced to lower case internally
if ( ! trim ( $files [ 'type' ][ $key ]))
$files [ 'type' ][ $key ] = 'Unknowm mime-type' ;
if ( UH_DEBUG )
{
2020-12-18 09:39:02 -08:00
e107 :: getLog () -> addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " Process file { $name } , size " . $files [ 'size' ][ $key ], FALSE , FALSE );
2019-03-29 19:13:32 -05:00
}
if ( $max_file_count && ( $c >= $max_file_count ))
{
$first_error = 249 ; // 'Too many files uploaded' error
}
else
{
$first_error = $files [ 'error' ][ $key ]; // Start with whatever error PHP gives us for the file
}
if ( ! $first_error )
{ // Check file size early on
if ( $files [ 'size' ][ $key ] == 0 )
{
$first_error = 4 ; // Standard error code for zero size file
}
elseif ( $files [ 'size' ][ $key ] > $max_upload_size )
{
$first_error = 254 ;
}
elseif ( isset ( $allowed_filetypes [ $file_ext ]) && ( $allowed_filetypes [ $file_ext ] > 0 ) && ( $files [ 'size' ][ $key ] > $allowed_filetypes [ $file_ext ]))
{ // XML file set limits per extension
$first_error = 254 ;
}
}
if ( ! $first_error )
{
$uploadfile = $files [ 'tmp_name' ][ $key ]; // Name in temporary directory
if ( ! $uploadfile )
$first_error = 253 ;
}
if ( ! $first_error )
{
// Need to support multiple files with the same 'real' name in some cases
if ( strpos ( $fileinfo , " attachment " ) === 0 )
{ // For attachments, add in a prefix plus time and date to give a unique file name
$addbit = explode ( '+' , $fileinfo , 2 );
$name = time () . " _ " . USERID . " _ " . trim ( $addbit [ 1 ]) . $name ;
}
elseif ( strpos ( $fileinfo , " prefix " ) === 0 )
{ // For attachments, avatars, photos etc alternatively just add a prefix we've been passed
$addbit = explode ( '+' , $fileinfo , 2 );
$name = trim ( $addbit [ 1 ]) . $name ;
}
$destination_file = $uploaddir . " / " . $name ;
if ( $fileinfo == " unique " && file_exists ( $destination_file ))
{ // Modify destination name to make it unique - but only if target file name exists
$name = time () . " _ " . $name ;
$destination_file = $uploaddir . " / " . $name ;
}
if ( file_exists ( $destination_file ) && ! $overwrite )
$first_error = 250 ; // Invent our own error number - duplicate file
}
if ( ! $first_error )
{
$tpos = FALSE ;
if ( $file_ext != '' ) // Require any uploaded file to have an extension
{
if ( $ul_temp_dir )
{ // Need to move file to our own temporary directory
$tempfilename = $uploadfile ;
$uploadfile = $ul_temp_dir . basename ( $uploadfile );
if ( UH_DEBUG )
{
2020-12-18 09:39:02 -08:00
e107 :: getLog () -> addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " Move { $tempfilename } to { $uploadfile } " , FALSE , LOG_TO_ROLLING );
2019-03-29 19:13:32 -05:00
}
@ move_uploaded_file ( $tempfilename , $uploadfile ); // This should work on all hosts
}
$tpos = (( $file_status = vet_file ( $uploadfile , $name , $allowed_filetypes , varset ( $options [ 'extra_file_types' ], FALSE ))) === TRUE );
}
if ( $tpos === FALSE )
{
// File type upload not permitted - error message and abort
$first_error = 251 ; // Invent our own error number - file type not permitted
}
}
if ( ! $first_error ) // All tests passed - can store it somewhere
{
// File upload broken - temp file renamed.
// FIXME - method starting with 'get' shouldn't do system file changes.
2020-12-14 16:21:48 -08:00
$uploaded [ $c ] = e107 :: getFile () -> getFileInfo ( $uploadfile , true , false );
2019-03-29 19:13:32 -05:00
$uploaded [ $c ][ 'name' ] = $name ;
$uploaded [ $c ][ 'rawname' ] = $raw_name ;
$uploaded [ $c ][ 'origname' ] = $origname ;
$uploaded [ $c ][ 'type' ] = $files [ 'type' ][ $key ];
$uploaded [ $c ][ 'size' ] = 0 ;
$uploaded [ $c ][ 'index' ] = $key ; // Store the actual index from the file_userfile array
// e107::getMessage()->addDebug(print_a($uploaded[$c],true));
// Store as flat file
if (( ! $ul_temp_dir && @ move_uploaded_file ( $uploadfile , $destination_file )) || ( $ul_temp_dir && @ rename ( $uploadfile , $destination_file ))) // This should work on all hosts
{
@ chmod ( $destination_file , $final_chmod );
if ( UH_DEBUG )
{
2020-12-18 09:39:02 -08:00
e107 :: getLog () -> addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " Final chmod() file { $destination_file } to { $final_chmod } " , FALSE , FALSE );
2019-03-29 19:13:32 -05:00
}
$uploaded [ $c ][ 'size' ] = $files [ 'size' ][ $key ];
$uploaded [ $c ][ 'fullpath' ] = $uploaddir . DIRECTORY_SEPARATOR . $name ;
if ( UH_DEBUG )
{
2020-12-18 09:39:02 -08:00
e107 :: getLog () -> addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " Saved file { $c } OK: " . $uploaded [ $c ][ 'name' ], FALSE , FALSE );
2019-03-29 19:13:32 -05:00
}
}
else
{
$first_error = 252 ; // Error - "couldn't save destination"
}
}
if ( ! $first_error ) // This file succeeded
{
$uploaded [ $c ][ 'message' ] = LANUPLOAD_3 . " ' " . $raw_name . " ' " ;
$uploaded [ $c ][ 'error' ] = 0 ;
}
else
{
$uploaded [ $c ][ 'error' ] = $first_error ;
$uploaded [ $c ][ 'size' ] = 0 ;
switch ( $first_error )
{
case 1 : // Exceeds upload_max_filesize in php.ini
$error = LANUPLOAD_5 ;
break ;
case 2 : // Exceeds MAX_FILE_SIZE in form
$error = LANUPLOAD_6 ;
break ;
case 3 : // Partial upload
$error = LANUPLOAD_7 ;
break ;
case 4 : // No file uploaded
$error = LANUPLOAD_8 ;
break ;
case 5 : // Undocumented code (zero file size)
$error = LANUPLOAD_9 ;
break ;
case 6 : // Missing temporary folder
$error = LANUPLOAD_13 ;
break ;
case 7 : // File write failed
$error = LANUPLOAD_14 ;
break ;
case 8 : // Upload stopped by extension
$error = LANUPLOAD_15 ;
break ;
case 249 : // Too many files (our error code)
$error = LANUPLOAD_19 ;
break ;
case 250 : // Duplicate File (our error code)
$error = LANUPLOAD_10 ;
break ;
case 251 : // File type not allowed (our error code)
$error = LANUPLOAD_1 . " " . $files [ 'type' ][ $key ] . " " . LANUPLOAD_2 . " ( { $file_status } ) " ;
break ;
case 252 : // File uploaded OK, but couldn't save it
$error = LANUPLOAD_4 . " [ " . str_replace ( " ../ " , " " , $uploaddir ) . " ] " ;
break ;
case 253 : // Bad name for uploaded file (our error code)
$error = LANUPLOAD_17 ;
break ;
case 254 : // file size exceeds allowable limits (our error code)
$error = LANUPLOAD_18 ;
break ;
default : // Shouldn't happen - but at least try and make it obvious if it does!
$error = LANUPLOAD_16 ;
}
$uploaded [ $c ][ 'message' ] = LANUPLOAD_11 . " ' " . $name . " ' <br /> " . LAN_ERROR . " : " . $error ;
$uploaded [ $c ][ 'line' ] = __LINE__ ;
$uploaded [ $c ][ 'file' ] = __FILE__ ;
if ( UH_DEBUG ) // If we need to abort on first error, do so here - could check for specific error codes
{
2020-12-18 09:39:02 -08:00
e107 :: getLog () -> addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " Main routine error { $first_error } file { $c } : " . $uploaded [ $c ][ 'message' ], FALSE , FALSE );
2019-03-29 19:13:32 -05:00
}
}
if ( is_file ( $uploadfile ))
{
@ unlink ( $uploadfile ); // Don't leave the file on the server if error (although should be auto-deleted)
}
$c ++ ;
}
}
return $uploaded ;
}
/**
* Utility routine to handle the messages returned by process_uploaded_files () .
* @ param array $upload_array is the list of uploaded files ( as returned by process_uploaded_files ())
* @ param boolean $errors_only - if TRUE , no message is shown for a successful upload .
* @ param boolean $use_handler - if TRUE , message_handler is used to display the message .
* @ return string - a list of all accumulated messages . ( Non - destructive call , so can be called several times with different options ) .
*/
function handle_upload_messages ( & $upload_array , $errors_only = TRUE , $use_handler = FALSE )
{
// Display error messages, accumulate FMESSAGE
// Write as a separate routine - returns all messages displayed. Option to only display failures.
2021-11-26 12:36:25 -08:00
$f_message = [];
2019-03-29 19:13:32 -05:00
foreach ( $upload_array as $k => $r )
{
if ( ! $errors_only || $r [ 'error' ])
{
if ( $use_handler )
{
require_once ( e_HANDLER . " message_handler.php " );
message_handler ( " MESSAGE " , $r [ 'message' ], $r [ 'line' ], $r [ 'file' ]);
}
$f_message [] = $r [ 'message' ];
}
}
return implode ( " <br /> " , $f_message );
}
/*
//====================================================================
// LEGACY FILE UPLOAD HANDLER
//====================================================================
/**
* This is the 'legacy' interface , which handles various special cases etc .
* It was the only option in E107 0.7 . 8 and earlier , and is still used in some places in core .
* It also attempts to return in the same way as the original , especially when any errors occur
* @ deprecated
* @ param string $uploaddir - target directory for file . Defaults to e_FILE / public
* @ param boolean | string $avatar - sets the 'type' or destination of the file :
* FALSE - its a 'general' file
* 'attachment' - indicates an attachment ( related to forum post or PM )
* 'unique' - indicates that file name must be unique - new name given ( prefixed with time () _ )
* 'avatar' - indicates an avatar is being uploaded
* @ param string $fileinfo - included within the name of the saved file with attachments - can be an identifier of some sort
* ( Forum adds 'FT{$tid}_' - where $tid is the thread ID .
* @ param boolean $overwrite - if true , an uploaded file can overwrite an existing file of the same name ( not used in 0.7 core )
*
* Preference used :
* $pref [ 'upload_storagetype' ] - now ignored - used to be 1 for files , 2 for database storage
*
* @ return boolean | array - For backward compatibility , returns FALSE if only one file uploaded and an error ;
* otherwise returns an array with per - file error codes as appropriate .
* On exit , F_MESSAGE is defined with the success / failure message ( s ) that have been displayed - one file per line
*/
/**
* @ Deprecated use e107 :: getFile () -> getUploaded ();
* @ param $uploaddir
* @ param bool | false $avatar
* @ param string $fileinfo
* @ param string $overwrite
* @ return array | bool
*/
function file_upload ( $uploaddir , $avatar = FALSE , $fileinfo = " " , $overwrite = " " )
{
2020-12-22 09:36:02 -08:00
$admin_log = e107 :: getLog ();
2019-03-29 19:13:32 -05:00
$options = array (
'extra_file_types' => TRUE
); // As default, allow any filetype enabled in filetypes.php
if ( ! $uploaddir )
{
$uploaddir = e_UPLOAD ;
}
if ( strpos ( $avatar , '=' ) !== FALSE )
{
list ( $avatar , $param ) = explode ( '=' , $avatar , 2 );
}
else
{
$param = USERID ;
}
switch ( $avatar )
{
case 'attachment' :
$avatar = " attachment+ " . $fileinfo ;
break ;
case 'avatar' :
$avatar = 'prefix+ap_' . $param . '_' ; // Prefix unique to user
$options [ 'overwrite' ] = TRUE ; // Allow update of avatar with same file name
break ;
}
if ( UH_DEBUG )
$admin_log ->
2020-12-18 09:39:02 -08:00
addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " Legacy call, directory " . $uploaddir , FALSE , FALSE );
2019-03-29 19:13:32 -05:00
$ret = process_uploaded_files ( getcwd () . " / " . $uploaddir , $avatar , $options ); // Well, that's the way it was done before
if ( $ret === FALSE )
{
if ( UH_DEBUG )
$admin_log ->
2020-12-18 09:39:02 -08:00
addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " Legacy return FALSE " , FALSE , FALSE );
2019-03-29 19:13:32 -05:00
return FALSE ;
}
if ( UH_DEBUG )
$admin_log ->
2020-12-18 09:39:02 -08:00
addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " Legacy return with " . count ( $ret ) . " files " , FALSE , FALSE );
2019-03-29 19:13:32 -05:00
$messages = handle_upload_messages ( $ret , FALSE , TRUE ); // Show all the error and acknowledgment messages
2022-12-10 07:06:47 -08:00
define ( 'F_MESSAGE' , $messages );
2019-03-29 19:13:32 -05:00
if ( count ( $ret ) == 1 )
{
if ( $ret [ 0 ][ 'error' ] != 0 )
return FALSE ; // Special case if errors
}
return $ret ;
}
//====================================================================
// VETTING AND UTILITY ROUTINES
//====================================================================
/**
2022-04-04 10:54:24 -07:00
* @ param $filename
* @ param bool | false $extended
* @ return array | string
*@ see e_file :: getImageMime ();
2019-03-29 19:13:32 -05:00
* or when extended - array [( string ) mime - type , ( array ) associated extensions )] .
* A much faster way to retrieve mimes than getimagesize ()
*
2022-04-04 10:54:24 -07:00
* @ deprecated Get image ( string ) mime type
*/
2019-03-29 19:13:32 -05:00
function get_image_mime ( $filename , $extended = false )
{
2020-12-22 14:48:28 -08:00
trigger_error ( __METHOD__ . ' is deprecated. Use e107::getFile()->getImageMime($filename, $extended); instead.' , E_USER_DEPRECATED );
2019-03-29 19:13:32 -05:00
2020-12-22 14:48:28 -08:00
return e107 :: getFile () -> getImageMime ( $filename , $extended );
2019-03-29 19:13:32 -05:00
}
/**
* Check uploaded file to try and identify dodgy content .
* @ param string $filename is the full path + name to the uploaded file on the server
* @ param string $target_name is the intended name of the file once transferred
* @ param array $allowed_filetypes is an array of permitted file extensions , in lower case , no leading '.'
* ( usually generated from filetypes . xml / filetypes . php )
* @ param boolean | string $unknown - handling of file types unknown to us / define additional types
* if FALSE , rejects totally unknown file extensions ( even if in $allowed_filetypes ) .
* if $unknown is TRUE , accepts totally unknown file extensions .
* otherwise $unknown is a comma - separated list of additional permitted file extensions
* @ return boolean | int - TRUE if file acceptable , a numeric 'fail' code if unacceptable :
* 1 - file type not allowed
* 2 - can ' t read file contents
* 3 - illegal file contents ( usually '<?php' )
* 4 - not an image file
* 5 - bad image parameters - REMOVED
* 6 - not in supplementary list
* 7 - suspicious file contents
* 8 - unknown file type
* 9 - unacceptable file type ( prone to exploits )
*/
//TODO - Move this function to file_class.php
function vet_file ( $filename , $target_name , $allowed_filetypes = '' , $unknown = false )
{
// 1. Start by checking against filetypes - that's the easy one!
$file_ext = strtolower ( substr ( strrchr ( $target_name , '.' ), 1 ));
if ( ! isset ( $allowed_filetypes [ $file_ext ]))
{
if ( is_bool ( $unknown ))
{
return 1 ;
} // Reject out of hand if no possible alternative extensions
// Otherwise, it could be in the supplementary list
$tmp = explode ( ',' , $unknown );
2020-12-20 11:50:10 -08:00
for ( $i = 0 , $iMax = count ( $tmp ); $i < $iMax ; $i ++ )
2019-03-29 19:13:32 -05:00
{
$tmp [ $i ] = strtolower ( trim ( str_replace ( '.' , '' , $tmp [ $i ])));
}
if ( ! in_array ( $file_ext , $tmp ))
{
return 6 ;
}
}
// 2. For all files, read the first little bit to check for any flags etc
$res = fopen ( $filename , 'rb' );
$tstr = fread ( $res , 2048 );
fclose ( $res );
if ( $tstr === false )
{
return 2 ; // If can't read file, not much use carrying on!
}
if ( stripos ( $tstr , '<?php' ) !== false )
{
return 3 ; // Pretty certain exploit
}
if ( strpos ( $tstr , '<?' ) !== false ) // Bit more tricky - can sometimes be OK
{
if ( stripos ( $tstr , '<?xpacket' ) === false ) // Allow the XMP header produced by CS4
{
return 7 ;
}
}
// 3. Now do what we can based on file extension
switch ( $file_ext )
{
case 'jpg' :
case 'gif' :
case 'png' :
case 'jpeg' :
case 'pjpeg' :
case 'bmp' :
case 'swf' :
case 'fla' :
case 'flv' :
case 'swc' :
case 'psd' :
case 'ai' :
case 'eps' :
case 'svg' :
case 'tiff' :
case 'jpc' : // http://fileinfo.com/extension/jpc
case 'jpx' : // http://fileinfo.com/extension/jpx
case 'jb2' : // http://fileinfo.com/extension/jb2
case 'jp2' : // http://fileinfo.com/extension/jp2
case 'iff' :
case 'wbmp' :
case 'xbm' :
case 'ico' :
$ret = get_image_mime ( $filename );
if ( $ret === false )
{
return 4 ; // exif_imagetype didn't recognize the image mime
}
// getimagesize() is extremely slow + it can't handle all required media!!! Abandon this check!
// return 5; // Zero size picture or bad file format
break ;
case 'zip' :
case 'gzip' :
case 'gz' :
case 'tar' :
case 'bzip' :
case 'pdf' :
case 'doc' :
case 'docx' :
case 'xls' :
case 'xlsx' :
case 'rar' :
case '7z' :
case 'csv' :
case 'mp3' :
case 'wav' :
case 'mp4' :
case 'mpg' :
case 'mpa' :
case 'wma' :
case 'wmv' :
2020-12-05 14:13:18 -08:00
// case 'flv': //Flash stream
2019-03-29 19:13:32 -05:00
case 'f4v' : //Flash stream
case 'mov' : //media
case 'avi' : //media
break ; // Just accept these
case 'php' :
case 'htm' :
case 'html' :
case 'cgi' :
case 'pl' :
return 9 ; // Never accept these! Whatever the user thinks!
default :
if ( is_bool ( $unknown ))
{
return ( $unknown ? true : 8 );
}
}
return true ; // Accepted here
}
/**
* Get array of file types ( file extensions ) which are permitted - reads a definition file .
* ( Similar to @ See { get_XML_filetypes ()}, but expects an XML file )
*
* @ param string $def_file - name of a file containing a comma - separated list of file types , which is sought in the E_ADMIN directory
* @ param string $file_mask - comma - separated list of allowed file types - only those specified in both $file_mask and $def_file are returned
*
* @ return array - where key is the file type ( extension ); value is max upload size
*/
function get_allowed_filetypes ( $def_file = FALSE , $file_mask = '' )
{
$ret = array (
);
if ( $def_file === FALSE )
return $ret ;
if ( $file_mask )
{
$file_array = explode ( ',' , $file_mask );
foreach ( $file_array as $k => $f )
{
$file_array [ $k ] = trim ( $f );
}
}
if ( $def_file && is_readable ( e_ADMIN . $def_file ))
{
$a_filetypes = trim ( file_get_contents ( e_ADMIN . $def_file ));
$a_filetypes = explode ( ',' , $a_filetypes );
}
foreach ( $a_filetypes as $ftype )
{
$ftype = strtolower ( trim ( str_replace ( '.' , '' , $ftype )));
if ( $ftype && ( ! $file_mask || in_array ( $ftype , $file_array )))
{
$ret [ $ftype ] = - 1 ;
}
}
return $ret ;
}
/**
* Parse a file size string ( e . g . 16 M ) and compute the simple numeric value .
* Proxy to e_file :: file_size_decode () .
*
* @ see e_file :: file_size_decode ()
* @ param string $source - input string which may include 'multiplier' characters such as 'M' or 'G' . Converted to 'decoded value'
* @ param int $compare - a 'compare' value
* @ param string $action - values ( gt | lt )
*
* @ return int file size value .
* If the decoded value evaluates to zero , returns the value of $compare
* If $action == 'gt' , return the larger of the decoded value and $compare
* If $action == 'lt' , return the smaller of the decoded value and $compare
*/
function file_size_decode ( $source , $compare = 0 , $action = '' )
{
return e107 :: getFile ( true ) -> file_size_decode ( $source , $compare , $action );
}
/**
* @ deprecated @ see e_file :: getAllowedFileTypes ();
* Get array of file types ( file extensions ) which are permitted - reads an XML - formatted definition file .
* ( Similar to @ See { get_allowed_filetypes ()}, but expects an XML file )
*
* @ param string $def_file - name of an XML - formatted file , which is sought in the E_ADMIN directory
* @ param string $file_mask - comma - separated list of allowed file types - only those specified in both $file_mask and $def_file are returned
*
* @ return array - where key is the file type ( extension ); value is max upload size
*/
function get_XML_filetypes ( $def_file = FALSE , $file_mask = '' )
{
2020-12-22 14:48:28 -08:00
trigger_error ( __METHOD__ . ' is deprecated. Use e107::getFile()->getAllowedFileTypes(); instead.' , E_USER_DEPRECATED );
2019-03-29 19:13:32 -05:00
2020-12-22 14:48:28 -08:00
return e107 :: getFile () -> getAllowedFileTypes ();
2019-03-29 19:13:32 -05:00
}
/**
* Calculate 'global' maximum upload size - the maximum before extension - specific restrictions taken into account
*
* @ param int $max_up - if > 0 , its a global maximum permitted . If < 0 , $pref [ 'upload_maxfilesize' ] is used ( if set )
*
* @ return int maximum allowed upload size for file
*/
function calc_max_upload_size ( $max_up = - 1 )
{
global $pref ;
2020-12-22 09:36:02 -08:00
$admin_log = e107 :: getLog ();
2019-03-29 19:13:32 -05:00
// Work out maximum allowable file size
if ( deftrue ( 'UH_DEBUG' ))
{
$admin_log ->
2020-12-18 09:39:02 -08:00
addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " File size limits - user set: " . $pref [ 'upload_maxfilesize' ] . " Post_max_size: " . ini_get ( 'post_max_size' ) . " upload_max_size: " . ini_get ( 'upload_max_size' ), FALSE , FALSE );
2019-03-29 19:13:32 -05:00
}
$max_upload_size = file_size_decode ( ini_get ( 'post_max_size' ));
$max_upload_size = file_size_decode ( ini_get ( 'upload_max_filesize' ), $max_upload_size , 'lt' );
if ( $max_up > 0 )
{
$max_upload_size = file_size_decode ( $max_up , $max_upload_size , 'lt' );
}
else
{
if ( varset ( $pref [ 'upload_maxfilesize' ], 0 ) > 0 )
$max_upload_size = file_size_decode ( $pref [ 'upload_maxfilesize' ], $max_upload_size , 'lt' );
}
if ( deftrue ( 'UH_DEBUG' ))
$admin_log ->
2020-12-18 09:39:02 -08:00
addEvent ( 10 , __FILE__ . " | " . __FUNCTION__ . " @ " . __LINE__ , " DEBUG " , " Upload Handler test " , " Final max upload size: { $max_upload_size } " , FALSE , FALSE );
2019-03-29 19:13:32 -05:00
return $max_upload_size ;
}
/**
* Get an array of permitted filetypes according to a set hierarchy .
* If a specific file name given , that ' s used . Otherwise the default hierarchy is used
*
* @ param string $file_mask - comma - separated list of allowed file types
* @ param string $filename - optional override file name - defaults ignored
*
* @ return array of filetypes
*/
function get_filetypes ( $file_mask = FALSE , $filename = '' )
{
if ( $filename != '' )
{
if ( strtolower ( substr ( $filename , - 4 ) == '.xml' ))
{
return get_XML_filetypes ( $filename , $file_mask );
}
return get_allowed_filetypes ( $filename , $file_mask );
}
if ( is_readable ( e_SYSTEM . e_READ_FILETYPES ))
{
return get_XML_filetypes ( e_READ_FILETYPES , $file_mask );
}
if ( ADMIN && is_readable ( e_ADMIN . 'admin_filetypes.php' ))
{
return get_allowed_filetypes ( 'admin_filetypes.php' , $file_mask );
}
if ( is_readable ( e_ADMIN . 'filetypes.php' ))
{
return get_allowed_filetypes ( 'filetypes.php' , $file_mask );
}
return array (); // Just an empty array
}
/**
* Scans the array of allowed file types , updates allowed max size as appropriate .
* If the value is larger than the site - wide maximum , reduces it .
*
* @ param array $allowed_filetypes - key is file type ( extension ), value is maximum size allowed
* @ param int $max_upload_size - site - wide maximum file upload size
*
* @ return int largest allowed file size across all file types
*/
function set_max_size ( & $allowed_filetypes , $max_upload_size )
{
$new_max = 0 ;
foreach ( $allowed_filetypes as $t => $s )
{
if ( $s < 0 )
{ // Unspecified max - use the global value
$allowed_filetypes [ $t ] = $max_upload_size ;
}
2021-12-03 14:58:33 -08:00
elseif ( $s > $max_upload_size )
2019-03-29 19:13:32 -05:00
$allowed_filetypes [ $t ] = $max_upload_size ;
if ( $allowed_filetypes [ $t ] > $new_max )
$new_max = $allowed_filetypes [ $t ];
}
return $new_max ;
}
/*
* Quick routine if all we want is the size of the largest file the current user can upload
*
* @ return int largest allowed file size across all file types
*/
2022-04-04 10:54:24 -07:00
/**
* @ return int
*/
function get_user_max_upload ()
2019-03-29 19:13:32 -05:00
{
$a_filetypes = get_filetypes ();
if ( count ( $a_filetypes ) == 0 )
return 0 ; // Return if no upload allowed
$max_upload_size = calc_max_upload_size ( - 1 ); // Find overriding maximum upload size
$max_upload_size = set_max_size ( $a_filetypes , $max_upload_size );
return $max_upload_size ;
}
2020-06-05 11:34:17 -07:00