diff --git a/wire/core/WireUpload.php b/wire/core/WireUpload.php index 8538faf3..82f918e2 100644 --- a/wire/core/WireUpload.php +++ b/wire/core/WireUpload.php @@ -6,7 +6,7 @@ * Saves uploads of single or multiple files, saving them to the destination path. * If the destination path does not exist, it will be created. * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2023 by Ryan Cramer * https://processwire.com * */ @@ -141,6 +141,12 @@ class WireUpload extends Wire { */ protected $errorInfo = array(); + /** + * @var bool + * + */ + protected $initialized = false; + /** * Construct with the given input name * @@ -148,7 +154,30 @@ class WireUpload extends Wire { * */ public function __construct($name) { + parent::__construct(); + $this->setName($name); + $this->maxFiles = 0; // no limit + $this->overwrite = false; + $this->destinationPath = ''; + } + + /** + * Wired to API + * + */ + public function wired() { + parent::wired(); + $this->init(); + } + + /** + * Initialize + * + */ + protected function init() { + $this->initialized = true; + $this->errorInfo = array( UPLOAD_ERR_OK => $this->_('Successful Upload'), UPLOAD_ERR_INI_SIZE => $this->_('The uploaded file exceeds the upload_max_filesize directive in php.ini.'), @@ -158,18 +187,15 @@ class WireUpload extends Wire { UPLOAD_ERR_NO_TMP_DIR => $this->_('Missing a temporary folder.'), UPLOAD_ERR_CANT_WRITE => $this->_('Failed to write file to disk.'), UPLOAD_ERR_EXTENSION => $this->_('File upload stopped by extension.') - ); - - $this->setName($name); - $this->maxFiles = 0; // no limit - $this->overwrite = false; - $this->destinationPath = ''; - - if($this->config->uploadBadExtensions) { - $badExtensions = $this->config->uploadBadExtensions; - if(is_string($badExtensions) && $badExtensions) $badExtensions = explode(' ', $badExtensions); - if(is_array($badExtensions)) $this->badExtensions = $badExtensions; - } + ); + + $config = $this->wire()->config; + + if($config->uploadBadExtensions) { + $badExtensions = $config->uploadBadExtensions; + if(is_string($badExtensions) && strlen($badExtensions)) $badExtensions = explode(' ', $badExtensions); + if(is_array($badExtensions)) $this->badExtensions = $badExtensions; + } } /** @@ -179,7 +205,7 @@ class WireUpload extends Wire { public function __destruct() { // cleanup files that were backed up when overwritten foreach($this->overwrittenFiles as $bakDestination => $destination) { - if(is_file($bakDestination)) $this->wire('files')->unlink($bakDestination); + if(is_file($bakDestination)) $this->wire()->files->unlink($bakDestination); } } @@ -192,13 +218,14 @@ class WireUpload extends Wire { */ public function execute() { + if(!$this->initialized) $this->init(); if(!$this->name) throw new WireException("You must set the name for WireUpload before executing it"); if(!$this->destinationPath) throw new WireException("You must set the destination path for WireUpload before executing it"); - $files = array(); + $uploadFiles = array(); $f = $this->getPhpFiles(); - if(!$f) return $files; + if(!$f) return $uploadFiles; if(is_array($f['name'])) { // multi file upload @@ -213,17 +240,17 @@ class WireUpload extends Wire { $cnt++; } - $files = $this->completedFilenames; + $uploadFiles = $this->completedFilenames; } else { // single file upload, including ajax if($this->isValidUpload($f['name'], $f['size'], $f['error'])) { $this->saveUpload($f['tmp_name'], $f['name'], !empty($f['ajax'])); // returns filename or false - $files = $this->completedFilenames; + $uploadFiles = $this->completedFilenames; } } - return $files; + return $uploadFiles; } /** @@ -249,7 +276,7 @@ class WireUpload extends Wire { */ protected function getUploadDir() { - $config = $this->wire('config'); + $config = $this->wire()->config; $dir = $config->uploadTmpDir; if(!$dir && stripos(PHP_OS, 'WIN') === 0) { @@ -283,8 +310,9 @@ class WireUpload extends Wire { */ protected function getPhpFilesAjax() { - if(!$filename = $_SERVER['HTTP_X_FILENAME']) return false; - $filename = rawurldecode($filename); // per #1487 + if(empty($_SERVER['HTTP_X_FILENAME'])) return false; + + $filename = rawurldecode($_SERVER['HTTP_X_FILENAME']); // per #1487 $dir = $this->getUploadDir(); $tmpName = tempnam($dir, wireClassName($this, false)); @@ -311,7 +339,7 @@ class WireUpload extends Wire { 'size' => $filesize, 'error' => $error, 'ajax' => true, - ); + ); return $file; } @@ -348,11 +376,12 @@ class WireUpload extends Wire { * */ protected function isValidUpload($name, $size, $error) { - $valid = false; - $fname = $this->wire('sanitizer')->name($name); + + $fname = $this->wire()->sanitizer->name($name); if($error && $error != UPLOAD_ERR_NO_FILE) { $this->error($this->errorInfo[$error]); + $valid = false; } else if(!$size) { $valid = false; // no data } else if($name[0] == '.') { @@ -362,8 +391,10 @@ class WireUpload extends Wire { "$fname - " . $this->_('Invalid file extension, please use one of:') . ' ' . implode(', ', $this->validExtensions) ); + $valid = false; } else if($this->maxFileSize > 0 && $size > $this->maxFileSize) { - $this->error("$fname - " . $this->_('Exceeds max allowed file size')); + $this->error("$fname - " . $this->_('Exceeds max allowed file size')); + $valid = false; } else { $valid = true; } @@ -379,7 +410,7 @@ class WireUpload extends Wire { */ protected function checkDestinationPath() { if(!is_dir($this->destinationPath)) { - $this->error("Destination path does not exist {$this->destinationPath}"); + $this->error("Destination path does not exist: $this->destinationPath"); return false; } return true; @@ -416,9 +447,11 @@ class WireUpload extends Wire { * */ public function validateFilename($value, $extensions = array()) { + $value = basename($value); if($value[0] == '.') return false; // no hidden files - $value = $this->wire('sanitizer')->filename($value, Sanitizer::translate); + + $value = $this->wire()->sanitizer->filename($value, Sanitizer::translate); if($this->lowercase) $value = strtolower($value); $value = trim($value, "_"); if(!strlen($value)) return false; @@ -506,11 +539,11 @@ class WireUpload extends Wire { if(!$destination || !$filename) $destination = $this->destinationPath . 'invalid-filename'; if(!$error) $error = "Unable to move uploaded file to: $destination"; $this->error($error); - if(is_file($tmp_name)) $this->wire('files')->unlink($tmp_name); + if(is_file($tmp_name)) $this->wire()->files->unlink($tmp_name); return false; } - $this->wire('files')->chmod($destination); + $this->wire()->files->chmod($destination); if($p['extension'] == 'zip' && ($this->maxFiles == 0) && $this->extractArchives) { if($this->saveUploadZip($destination)) { @@ -536,30 +569,35 @@ class WireUpload extends Wire { // unzip with command line utility + $fileTools = $this->wire()->files; $files = array(); $dir = dirname($zipFile) . '/'; $tmpDir = $dir . '.zip_tmp/'; try { - $files = $this->wire('files')->unzip($zipFile, $tmpDir); + $files = $fileTools->unzip($zipFile, $tmpDir); if(!count($files)) { throw new WireException($this->_('No files found in ZIP file')); } } catch(\Exception $e) { $this->error($e->getMessage()); - $this->wire('files')->rmdir($tmpDir, true); - $this->wire('files')->unlink($zipFile); + $fileTools->rmdir($tmpDir, true); + $fileTools->unlink($zipFile); return $files; } $cnt = 0; + + // allow files within ZIP to be up to 10x the size of max upload size + $maxFileSize = $this->maxFileSize; + $this->setMaxFileSize($maxFileSize * 10); foreach($files as $file) { $pathname = $tmpDir . $file; if(!$this->isValidUpload($file, filesize($pathname), UPLOAD_ERR_OK)) { - $this->wire('files')->unlink($pathname, $tmpDir); + $fileTools->unlink($pathname, $tmpDir); continue; } @@ -575,7 +613,7 @@ class WireUpload extends Wire { $bakDestination = $dir . $bakName; } while(file_exists($bakDestination)); rename($destination, $bakDestination); - $this->wire('log')->message("Renamed $destination => $bakDestination"); + $this->wire()->log->message("Renamed $destination => $bakDestination"); $this->overwrittenFiles[$bakDestination] = $destination; } else { @@ -589,15 +627,16 @@ class WireUpload extends Wire { $this->completedFilenames[] = basename($destination); $cnt++; } else { - $this->wire('files')->unlink($pathname, $tmpDir); + $fileTools->unlink($pathname, $tmpDir); } } + + $this->setMaxFileSize($maxFileSize); // restore original - $this->wire('files')->rmdir($tmpDir, true); - $this->wire('files')->unlink($zipFile); + $fileTools->rmdir($tmpDir, true); + $fileTools->unlink($zipFile); - if(!$cnt) return false; - return true; + return $cnt > 0; } /** @@ -732,7 +771,7 @@ class WireUpload extends Wire { * */ public function setName($name) { - $this->name = $this->wire('sanitizer')->fieldName($name); + $this->name = $this->wire()->sanitizer->fieldName($name); return $this; }