Seditio Source
Root |
./othercms/b2evolution_7.2.3/htsrv/upload_handler.php
<?php
/**
 * Do not use or reference this directly from your client-side code.
 * Instead, this should be required via the endpoint.php or endpoint-cors.php
 * file(s).
 */
class UploadHandler {
    public
$allowedExtensions = array();
    public
$sizeLimit = null;
    public
$inputName = 'qqfile';
    public
$chunksFolder = 'chunks';
    public
$chunksCleanupProbability = 0.001; // Once in 1000 requests on avg
   
public $chunksExpireIn = 604800; // One week
   
protected $uploadName;

   
/**
        * Get the original filename
        */
   
public function getName()
    {
            if( isset(
$_REQUEST['qqfilename'] ) )
                    return
$_REQUEST['qqfilename'];
            if( isset(
$_FILES[$this->inputName] ) )
                    return
$_FILES[$this->inputName]['name'];
    }

    public function
getInitialFiles()
    {
       
$initialFiles = array();
        for(
$i = 0; $i < 5000; $i++ )
        {
           
array_push( $initialFiles, array(
               
"name" => "name" + $i,
               
uuid => "uuid" + $i,
               
thumbnailUrl => "/test/dev/handlers/vendor/fineuploader/php-traditional-server/fu.png" ) );
        }

        return
$initialFiles;
    }

   
/**
        * Get the name of the uploaded file
        */
   
public function getUploadName()
    {
            return
$this->uploadName;
    }

    public function
combineChunks( $name = null )
    {
       
$uuid = $_POST['qquuid'];

        if(
$name === null )
        {
           
$name = $this->getName();
        }

       
$targetFolder = $this->chunksFolder.DIRECTORY_SEPARATOR.$uuid;
       
$totalParts = isset( $_REQUEST['qqtotalparts'] ) ? ( int ) $_REQUEST['qqtotalparts'] : 1;
       
$targetPath = join( DIRECTORY_SEPARATOR, array( $uuid, $name ) );
       
$this->uploadName = $name;

        if ( !
file_exists( $targetPath ) )
        {
           
mkdir( dirname( $targetPath ), 0777, true );
        }

       
$target = fopen( $targetPath, 'wb' );
        for(
$i = 0; $i < $totalParts; $i++ )
        {
           
$chunk = fopen( $targetFolder.DIRECTORY_SEPARATOR.$i, "rb" );
           
stream_copy_to_stream( $chunk, $target );
           
fclose ($chunk );
        }

       
// Success
       
fclose( $target );

       
$target = fopen( $targetPath, 'rb' );
       
fseek( $temp, 0, SEEK_SET );
       
$contents = '';

       
load_funcs( 'tools/model/_system.funcs.php' );
       
$memory_limit = system_check_memory_limit();

        while( !
feof( $target ) )
        {
            if(
$memory_limit != -1 && ( $memory_limit - memory_get_usage( true ) ) < 8192 )
            {
// Don't try to load the next portion of image into the memory because it would cause 'Allowed memory size exhausted' error
               
fclose( $temp );
                return array(
'error' => T_('Out of memory.') );
            }
           
$contents .= fread( $target, 8192 );
        }

       
fclose( $target );

       
// Delete chunks
       
for( $i = 0; $i < $totalParts; $i++ )
        {
           
unlink( $targetFolder.DIRECTORY_SEPARATOR.$i );
        }
       
rmdir( $targetFolder );
        if ( !
is_null( $this->sizeLimit ) && filesize( $targetPath ) > $this->sizeLimit )
        {
           
unlink( $targetPath );
           
http_response_code( 413 );
            return array(
"success" => false, "uuid" => $uuid, "preventRetry" => true );
        }

       
// Delete combined chunks
       
unlink( $target );

        return array(
"success" => true, "contents" => $contents, "uuid" => $uuid );
    }

   
/**
        * Process the upload.
        * @param string $name Overwrites the name of the file.
        */
   
public function handleUpload( $name = null )
    {
        if(
is_writable( $this->chunksFolder ) && 1 == mt_rand( 1, 1 / $this->chunksCleanupProbability ) )
        {
           
// Run garbage collection
           
$this->cleanupChunks();
        }

       
// Check that the max upload size specified in class configuration does not
        // exceed size allowed by server config
       
if( $this->toBytes( ini_get( 'post_max_size' ) ) < $this->sizeLimit || $this->toBytes( ini_get( 'upload_max_filesize') ) < $this->sizeLimit )
        {
           
$neededRequestSize = max( 1, $this->sizeLimit / 1024 / 1024 ) . 'M';
            return array(
'error' => sprintf( /* NO TRANS for sysadmins */ 'Server error. Increase post_max_size and upload_max_filesize to %s', $neededRequestSize ) );
        }

       
$type = $_SERVER['CONTENT_TYPE'];
        if( isset(
$_SERVER['HTTP_CONTENT_TYPE'] ) )
        {
           
$type = $_SERVER['HTTP_CONTENT_TYPE'];
        }

        if( !isset(
$type ) )
        {
            return array(
'error' => T_('No file was uploaded.') );
        }
        elseif (
strpos( strtolower( $type ), 'multipart/' ) !== 0 )
        {
            return array(
'error' => /* NO TRANS for sysadmins */ 'Server error. Not a multipart request. Please set forceMultipart to default value (true).' );
        }

       
// Check for wrong uploading file:
       
if( ! isset( $_FILES[$this->inputName]['tmp_name'] ) ||
            !
is_uploaded_file( $_FILES[$this->inputName]['tmp_name'] ) )
        {    
// Deny uploading of wrong file:
           
return array( 'error' => 'Invalid '.$this->inputName.' file!' );
        }

       
// Get size and name
       
$file = $_FILES[$this->inputName];
       
$size = $file['size'];
        if( isset(
$_REQUEST['qqtotalfilesize'] ) )
        {
           
$size = $_REQUEST['qqtotalfilesize'];
        }

        if (
$name === null )
        {
           
$name = $this->getName();
        }

       
// check file error
       
if( $file['error'] )
        {
            return array(
'error' => sprintf( T_('Upload Error #%s'), $file['error'] ) );
        }

       
// Validate name
       
if($name === null || $name === '')
        {
            return array(
'error' => T_('File name empty.') );
        }
       
// Validate file size
       
if( $size == 0 )
        {
            return array(
'error' => T_('File is empty.') );
        }
        if ( !
is_null( $this->sizeLimit ) && $size > $this->sizeLimit )
        {
            return array(
'error' => T_('File is too large.'), 'preventRetry' => true );
        }

       
// Validate file extension
       
$pathinfo = pathinfo( $name );
       
$ext = isset( $pathinfo['extension'] ) ? $pathinfo['extension'] : '';
        if(
$this->allowedExtensions && ! in_array( strtolower( $ext ), array_map( "strtolower", $this->allowedExtensions ) ) )
        {
           
$these = implode( ', ', $this->allowedExtensions );
            return array(
'error' => sprintf( T_('File has an invalid extension, it should be one of %s.'), $these ) );
        }

        if( empty(
$_REQUEST['qquuid'] ) )
        {    
// Param qquuid must be passed:
           
return array( 'error' => 'Parameter "qquuid" is required!' );
        }

       
// Save a chunk
       
$totalParts = isset( $_REQUEST['qqtotalparts'] ) ? ( int ) $_REQUEST['qqtotalparts'] : 1;
       
$uuid = $_REQUEST['qquuid'];
        if(
$totalParts > 1 )
        {
# chunked upload
           
$chunksFolder = $this->chunksFolder;
           
$partIndex = ( int ) $_REQUEST['qqpartindex'];
            if( !
is_writable( $chunksFolder ) )
            {
                return array(
'error' => /* NO TRANS for sysadmins */ 'Server error. Chunks directory isn\'t writable or executable.' );
            }
           
$targetFolder = $this->chunksFolder.DIRECTORY_SEPARATOR.$uuid;
            if( !
file_exists( $targetFolder ) )
            {
               
mkdir( $targetFolder, 0777, true );
            }
           
$target = $targetFolder.'/'.$partIndex;
           
$success = move_uploaded_file( $file['tmp_name'], $target );

            return array(
"success" => true, "uuid" => $uuid );
        }
        else
        {
# non-chunked upload
           
$temp = fopen( $file['tmp_name'], "rb" );
           
fseek( $temp, 0, SEEK_SET );
           
$contents = '';

           
load_funcs( 'tools/model/_system.funcs.php' );
           
$memory_limit = system_check_memory_limit();

            while( !
feof( $temp ) )
            {
                if(
$memory_limit != -1 && ( $memory_limit - memory_get_usage( true ) ) < 8192 )
                {
// Don't try to load the next portion of image into the memory because it would cause 'Allowed memory size exhausted' error
                   
fclose( $temp );
                    return array(
'error' => T_('Out of memory.') );
                }
               
$contents .= fread( $temp, 8192 );
            }

           
fclose( $temp );

            return array(
'success' => true, 'contents' => $contents, 'uuid' => $uuid );
        }
    }

   
/**
        * Process a delete.
        * @param string $uploadDirectory Target directory.
        * @params string $name Overwrites the name of the file.
        *
        */
   
public function handleDelete( $uploadDirectory, $name = null )
    {
        if(
$this->isInaccessible( $uploadDirectory ) )
        {
            return array(
'error' => /* NO TRANS for sysadmins */ 'Server error. Uploads directory isn\'t writable'. ( ( ! $this->isWindows() ) ? ' or executable.' : '.' ) );
        }
       
$targetFolder = $uploadDirectory;
       
$url = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
       
$tokens = explode( '/', $url );
       
$uuid = $tokens[sizeof( $tokens ) - 1];
       
$target = join( DIRECTORY_SEPARATOR, array( $targetFolder, $uuid ) );

        if(
is_dir( $target ) )
        {
           
$this->removeDir( $target );
            return array(
'success' => true, 'uuid' => $uuid );
        }
        else
        {
            return array(
               
'success' => false,
               
'error' => T_('File not found! Unable to delete. ').$url,
               
'path' => $uuid
           
);
        }
    }

   
/**
        * Returns a path to use with this upload. Check that the name does not exist,
        * and appends a suffix otherwise.
        * @param string $uploadDirectory Target directory
        * @param string $filename The name of the file to use.
        */
   
protected function getUniqueTargetPath($uploadDirectory, $filename)
    {
       
// Allow only one process at the time to get a unique file name, otherwise
        // if multiple people would upload a file with the same name at the same time
        // only the latest would be saved.
       
if( function_exists( 'sem_acquire' ) )
        {
           
$lock = sem_get( ftok( __FILE__, 'u' ) );
           
sem_acquire( $lock );
        }
       
$pathinfo = pathinfo( $filename );
       
$base = $pathinfo['filename'];
       
$ext = isset( $pathinfo['extension'] ) ? $pathinfo['extension'] : '';
       
$ext = $ext == '' ? $ext : '.' . $ext;
       
$unique = $base;
       
$suffix = 0;

       
// Get unique file name for the file, by appending random suffix.
       
while( file_exists( $uploadDirectory . DIRECTORY_SEPARATOR . $unique . $ext ) )
        {
           
$suffix += rand( 1, 999 );
           
$unique = $base.'-'.$suffix;
        }
       
$result =  $uploadDirectory . DIRECTORY_SEPARATOR . $unique . $ext;

       
// Create an empty target file
       
if( ! touch( $result ) )
        {
           
// Failed
           
$result = false;
        }
        if(
function_exists( 'sem_acquire' ) )
        {
           
sem_release( $lock );
        }

        return
$result;
    }

   
/**
        * Deletes all file parts in the chunks folder for files uploaded
        * more than chunksExpireIn seconds ago
        */
   
protected function cleanupChunks()
    {
        foreach(
scandir( $this->chunksFolder ) as $item )
        {
            if(
$item == "." || $item == ".." )
            {
                continue;
            }
           
$path = $this->chunksFolder.DIRECTORY_SEPARATOR.$item;
            if( !
is_dir( $path ) )
            {
                continue;
            }
            if(
time() - filemtime( $path ) > $this->chunksExpireIn )
            {
               
$this->removeDir( $path );
            }
        }
    }

   
/**
        * Removes a directory and all files contained inside
        * @param string $dir
        */
   
protected function removeDir( $dir )
    {
        foreach(
scandir( $dir ) as $item )
        {
            if(
$item == "." || $item == ".." )
            {
                continue;
            }

            if(
is_dir( $item ) )
            {
               
$this->removeDir( $item );
            }
            else
            {
               
unlink( join( DIRECTORY_SEPARATOR, array( $dir, $item ) ) );
            }
        }
       
rmdir( $dir );
    }

   
/**
        * Converts a given size with units to bytes.
        * @param string $str
        */
   
protected function toBytes( $str )
    {
       
$str = trim( $str );
       
$val = intval( $str );
       
$last = strtolower( $str[strlen( $str ) - 1] );
        switch(
$last )
        {
            case
'g': $val *= 1024;
            case
'm': $val *= 1024;
            case
'k': $val *= 1024;
        }
        return
$val;
    }

   
/**
        * Determines whether a directory can be accessed.
        *
        * is_executable() is not reliable on Windows prior PHP 5.0.0
        *  (http://www.php.net/manual/en/function.is-executable.php)
        * The following tests if the current OS is Windows and if so, merely
        * checks if the folder is writable;
        * otherwise, it checks additionally for executable status (like before).
        *
        * @param string $directory The target directory to test access
        */
   
protected function isInaccessible( $directory )
    {
       
$isWin = $this->isWindows();
       
$folderInaccessible = ( $isWin ) ? ! is_writable( $directory ) : ( ! is_writable( $directory ) && ! is_executable( $directory ) );
        return
$folderInaccessible;
    }

   
/**
        * Determines is the OS is Windows or not
        *
        * @return boolean
        */
   
protected function isWindows()
    {
       
$isWin = (strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' );
        return
$isWin;
    }
}
?>