<?php
/**
* Uploader class
* managed by chunk.php
*
* @category PHP
* @package VenoFileManager
* @author Nicola Franchini <info@veno.it>
* @license Exclusively sold on CodeCanyon
* @link http://filemanager.veno.it/
*/
if (!class_exists('Uploader', false)) {
/**
* Uploader class
*
* @category PHP
* @package VenoFileManager
* @author Nicola Franchini <info@veno.it>
* @license Exclusively sold on CodeCanyon
* @link http://filemanager.veno.it/
*/
class Uploader
{
/**
* Verify a file to be uploaded
*
* @param string $fullfilepath path to final upload
* @param inft $filesize the final size
* @param boolean $remote if remote check also remote_extensions
*
* @return error response
*/
public static function veryFile($fullfilepath, $filesize, $remote = false)
{
global $setUp;
$filepathinfo = Utils::mbPathinfo($fullfilepath);
$filename = $filepathinfo['filename'];
$basename = $filepathinfo['basename'];
$extension = strtolower(Utils::getFileExtension($fullfilepath));
$allowed_ext = $remote ? $setUp->getConfig('remote_extensions') : $setUp->getConfig('upload_allow_type');
$rejected_ext = $remote ? array() : $setUp->getConfig('upload_reject_extension');
$rejected_files = $setUp->getConfig('hidden_files');
$upload_limit = $setUp->getConfig('max_upload_filesize');
if (!$extension) {
Utils::setError('<span><i class="bi bi-exclamation-triangle"></i> <strong>'.$basename.'</strong>: '.$setUp->getLangString('file_extension_missing').'</span> ');
return false;
}
if ((!$allowed_ext && !$rejected_ext)
|| ($allowed_ext && !Utils::inList($extension, $allowed_ext))
|| ($rejected_ext && Utils::inList($extension, $rejected_ext))
|| Utils::inList($basename, $rejected_files)
|| substr($basename, 0, 1) === "."
) {
Utils::setError('<span><i class="bi bi-exclamation-triangle"></i> '.$filename.'<strong>.'.$extension.'</strong> '.$setUp->getLangString('not_allowed').'</span> ');
return false;
}
if (Utils::fileExists($fullfilepath)) {
if (!$setUp->getConfig('overwrite_files') || $setUp->getConfig('overwrite_files') == 'no') {
Utils::setWarning('<span><i class="bi bi-info-circle"></i> <strong>'.$basename.'</strong> '.$setUp->getLangString('file_exists').'</span> ');
return false;
}
}
if (Actions::checkUserSpace(false, $filesize, true) === false) {
$max_upload_response = $upload_limit > 0 ? 'max '.$upload_limit.' MB' : $basename;
Utils::setError(
'<span><i class="bi bi-exclamation-triangle"></i> '.$setUp->getLangString('upload_exceeded').': <strong>'.$max_upload_response.'</strong></span> '
);
return false;
}
return true;
}
/**
* Append .txt to extension
*
* @param string $name name to modify
* @param string $extension extension to check
*
* @return string $name filename with .txt appended
*/
public static function safeExtension($name, $extension)
{
$evil = array(
'php','php3','php4','php5','htm','html','phtm','phtml',
'shtm','shtml','asp','pl','py','jsp','sh','cgi','htaccess',
'htpasswd','386','bat','cmd','pl','ddl','bin', 'asa', 'cer', 'xap',
);
if (in_array(strtolower($extension), $evil)) {
$name = $name.'.txt';
}
return $name;
}
/**
* Setup filename to upload
*
* @param string $resumableFilename filename to convert
*
* @return resumableFilename updated
*/
public function setupFilename($resumableFilename)
{
global $setUp;
$extension = Utils::getFileExtension($resumableFilename);
$filepathinfo = Utils::mbPathinfo($resumableFilename);
$basename = Utils::normalizeStr(Utils::checkMagicQuotes($filepathinfo['filename']));
$resumableFilename = $basename.'.'.$extension;
// Append date-time to file name.
$finalFilename = $setUp->getConfig('overwrite_files') === 'date' ? $basename.'_'.date('Y-m-d_G-i-s').'.'.$extension : $resumableFilename;
// $resumableFilename = $basename.'_'.date('Y-m-d_G-i-s').'.'.$extension;
$resumabledata = array();
$resumabledata['extension'] = $extension;
$resumabledata['basename'] = $basename;
$resumabledata['filename'] = $resumableFilename;
$resumabledata['finalname'] = $finalFilename;
return $resumabledata;
}
/**
* Check if all the parts exist, and
*
* @param string $temp_dir the temporary directory holding all the parts of the file
* @param int $totalSize original file size (in bytes)
* @param int $chunkSize chunk size (in bytes)
* @param int $resumableTotalChunks total chunk number
*
* @return uploaded file
*/
public function checkChunks($temp_dir, $totalSize, $chunkSize, $resumableTotalChunks)
{
// count all the parts of this file
$tempfiles = preg_grep('/^([^.])/', scandir($temp_dir));
if (is_array($tempfiles)) {
$total_files = count($tempfiles);
if ($total_files === $resumableTotalChunks) {
if ($total_files * $chunkSize >= ($totalSize - $chunkSize + 1)) {
return true;
}
}
}
return false;
}
/**
* Gather all the parts of the file together
*
* @param string $destination the final destination
* @param string $temp_dir the temporary directory holding all the parts of the file
* @param string $fileName the original file name
* @param string $totalSize original file size (in bytes)
* @param string $totalChunks total chunks number
* @param string $logloc relative location for log file
* @param string $finalFilename the final file name
* @param string $relativePath the path of the file subfolder
*
* @return uploaded file
*/
public function createFileFromChunks($destination, $temp_dir, $fileName, $totalSize, $totalChunks, $logloc, $finalFilename, $relativePath)
{
if (Uploader::veryFile($fileName, $totalSize) !== true) {
Actions::deleteDir($temp_dir);
return false;
}
global $setUp;
$upload_dir = str_replace('\\', '', $destination);
$extension = Utils::getFileExtension($finalFilename);
$finalfile = Uploader::safeExtension($finalFilename, $extension);
if (file_exists($upload_dir.$finalfile) && (!$setUp->getConfig('overwrite_files') || $setUp->getConfig('overwrite_files') == 'no')) {
Utils::setWarning('<span><i class="bi bi-info-circle"></i> <strong>'.$fileName.'</strong> '.$setUp->getLangString('file_exists').'</span> ');
if (rename($temp_dir, $temp_dir.'_UNUSED')) {
Actions::deleteDir($temp_dir.'_UNUSED');
} else {
Actions::deleteDir($temp_dir);
}
return false;
}
// create the final file
if (is_dir($upload_dir) && ($openfile = fopen($upload_dir.$finalfile, 'w')) !== false) {
// $log_start_time = microtime(true);
for ($i=1; $i<=$totalChunks; $i++) {
/*
* Old method higher memory usage
*/
// $logmethod = 'chunked fwrite';
// if (($stream_chunk = fopen($temp_dir.'/'.$fileName.'.part'.$i, 'r')) !== false) {
// while (!feof($stream_chunk)) {
// fwrite($openfile, fread($stream_chunk, 1048576));
// }
// }
/*
* stream_copy_to_stream method lower memory usage
*/
// $logmethod = 'stream copy';
if (($stream_chunk = fopen($temp_dir.'/'.$fileName.'.part'.$i, 'r')) !== false) {
stream_copy_to_stream($stream_chunk, $openfile);
fclose($stream_chunk);
}
}
fclose($openfile);
// track upload performance
// $this->logUpload($log_start_time, $logmethod, $totalSize);
// rename the temporary directory (to avoid access from other concurrent chunks uploads) and then delete it
if (rename($temp_dir, $temp_dir.'_UNUSED')) {
Actions::deleteDir($temp_dir.'_UNUSED');
} else {
Actions::deleteDir($temp_dir);
}
Utils::setSuccess(' <span><i class="bi bi-check-circle"></i> <strong>'.$relativePath.$finalfile.'</strong></span> ');
Actions::updateUserSpace(false, false, $totalSize);
Logger::logCreation($logloc.$finalfile, false);
} else {
Utils::setError(' <span><i class="bi bi-exclamation-triangle"></i> cannot create the destination file inside of<strong>'.basename($upload_dir).'</strong></span>');
return false;
}
}
/**
* Track upload performance
*
* @param int $log_start_time intial function time
* @param string $logmethod method used
* @param string $totalSize total file size
*
* @return the size of the remote file
*/
public function logUpload($log_start_time, $logmethod, $totalSize)
{
$log_elapsed = (microtime(true) - $log_start_time);
$log = PHP_EOL.'********************************************'.PHP_EOL;
$log = date('d/m/Y - G:i:s').PHP_EOL;
$log .= 'Method: '.$logmethod.PHP_EOL;
$log .= 'File size: '.($totalSize/1024/1024).' MB'.PHP_EOL;
$log .= 'Memory: '.(memory_get_usage(true)/1024/1024).' MB'.PHP_EOL;
$log .= 'Memory peak: '.(memory_get_peak_usage(true)/1024/1024).' MB'.PHP_EOL;
$log .= 'Elapsed time: '.$log_elapsed.PHP_EOL;
$log .= '********************************************'.PHP_EOL.PHP_EOL;
file_put_contents('vfm_uploader_log.txt', $log, FILE_APPEND);
}
/**
* Get size of remote file
*
* @param string $url Url to check
*
* @return the size of the remote file
*/
public function getRemoteSize($url)
{
global $setUp;
$totalsize = false;
$avoid = false;
if (filter_var($url, FILTER_VALIDATE_URL)) {
$cinit = curl_init();
curl_setopt_array(
$cinit,
array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_NOBODY => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FOLLOWLOCATION => false,
)
);
$data = curl_exec($cinit);
if (curl_error($cinit)) {
Utils::setError(curl_error($cinit));
} else {
// Check if response is OK
$httpCode = curl_getinfo($cinit, CURLINFO_HTTP_CODE);
if ($httpCode !== 200) {
$message = Uploader::getHeaderError($data);
Utils::setError(' <span><i class="bi bi-exclamation-triangle"></i> '.$message.'</span>');
$avoid = true;
}
// Check if is not a web page
$type = curl_getinfo($cinit, CURLINFO_CONTENT_TYPE);
$htmltype = strpos(strtolower($type), 'text/html');
if ($htmltype !== false) {
Utils::setError(' <span><i class="bi bi-exclamation-triangle"></i> '.$setUp->getLangString('invalid_link').'</span>');
$avoid = true;
}
if (!$avoid) {
$totalsize = curl_getinfo($cinit, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
}
}
curl_close($cinit);
}
return $totalsize;
}
/**
* Prepare multiple files for upload in the classic way
*
* @param array $coda $_FLES['userfile']
*
* @return call uploadFIle()
*/
public static function uploadMulti($coda)
{
global $location;
if ($location->editAllowed()
&& GateKeeper::isAllowed('upload_enable')
) {
// Number of files to uploaded
$num_files = count($coda['tmp_name']);
$totnames = array();
for ($i=0; $i < $num_files; $i++) {
$filepathinfo = Utils::mbPathinfo($coda['name'][$i]);
$filename = $filepathinfo['filename'];
$filex = $filepathinfo['extension'];
$thename = $filepathinfo['basename'];
$tempname = $coda['tmp_name'][$i];
$filerror = $coda['error'][$i];
$size = $coda['size'][$i];
if (in_array($thename, $totnames)) {
$thename = $filename.$i.".".$filex;
}
array_push($totnames, $thename);
if ($thename) {
Uploader::uploadFile($location, $thename, $tempname, $size);
// check uplad errors
Uploader::upLog($filerror);
}
}
}
}
/**
* Upload single file
*
* @param string $location where to upload
* @param string $thename file name
* @param string $tempname temp name
* @param int $size file size
*
* @return uploads file
*/
public static function uploadFile($location, $thename, $tempname, $size)
{
global $setUp;
$extension = Utils::getFileExtension($thename);
$filepathinfo = Utils::mbPathinfo($thename);
$basename = Utils::normalizeStr($filepathinfo['filename']);
$name = $setUp->getConfig('overwrite_files') === 'date' ? $basename.'_'.date('Y-m-d_G-i-s').'.'.$extension : $basename.'.'.$extension;
$upload_dir = $location->getFullPath();
$upload_file = $upload_dir.$name;
if (!$location->editAllowed() || !$location->isWritable()) {
Utils::setError('<span><i class="bi bi-exclamation-triangle"></i> '.$setUp->getString('upload_not_allowed').'</span> ');
return false;
}
if (Uploader::veryFile($upload_file, $size) == true) {
$clean_file = $upload_dir.Uploader::safeExtension($name, $extension);
if (!is_uploaded_file($tempname)) {
Utils::setError($setUp->getString('failed_upload'));
} elseif (!move_uploaded_file($tempname, $clean_file)) {
Utils::setError($setUp->getString('failed_move'));
} else {
Utils::setSuccess('<span><i class="bi bi-check-circle"></i> <strong>'.$name.'</strong></span> ');
// file successfully uploaded, sending log notification
Logger::logCreation($location->getDir(true, false, false, 0).$name, false);
}
}
}
/**
* Add log uploading errors to old html uploader
*
* @param num $filerr array value of $_FILES['userfile']['error'][$i]
*
* @return error response
*/
public static function upLog($filerr)
{
$error_types = array(
0=>'OK',
1=>'The uploaded file exceeds the upload_max_filesize directive in php.ini.',
2=>'The uploaded file exceeds the MAX_FILE_SIZE specified in the HTML form.',
3=>'The uploaded file was only partially uploaded.',
4=>'No file was uploaded.',
6=>'Missing a temporary folder.',
7=>'Failed to write file to disk.',
8=>'A PHP extension stopped the file upload.',
'post_max_size' => 'The uploaded file exceeds the post_max_size directive in php.ini',
'max_file_size' => 'File is too big',
'min_file_size' => 'File is too small',
'accept_file_types' => 'Filetype not allowed',
'max_number_of_files' => 'Maximum number of files exceeded',
'max_width' => 'Image exceeds maximum width',
'min_width' => 'Image requires a minimum width',
'max_height' => 'Image exceeds maximum height',
'min_height' => 'Image requires a minimum height',
'abort' => 'File upload aborted',
'image_resize' => 'Failed to resize image'
);
$error_message = $error_types[$filerr];
if ($filerr > 0) {
Utils::setError(' :: '.$error_message);
}
}
/**
* Get first header from remote file data
*
* @param string $data to parse
*
* @return error response
*/
public static function getHeaderError($data)
{
list($header, $body) = explode("\r\n\r\n", $data, 2);
$header = explode("\r\n", $header);
return $header[0];
}
/**
* Remove query string
*
* @param string $url url to modify
*
* @return string $Return url without ?...=
*/
public static function removeQueryString($url)
{
$parts = explode('?', $url);
return $parts[0];
}
}
}