<?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]; } } }