1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Validator\File; 11 12use Zend\Validator\AbstractValidator; 13use Zend\Validator\Exception; 14 15/** 16 * Validator for the maximum size of a file up to a max of 2GB 17 * 18 */ 19class Upload extends AbstractValidator 20{ 21 /** 22 * @const string Error constants 23 */ 24 const INI_SIZE = 'fileUploadErrorIniSize'; 25 const FORM_SIZE = 'fileUploadErrorFormSize'; 26 const PARTIAL = 'fileUploadErrorPartial'; 27 const NO_FILE = 'fileUploadErrorNoFile'; 28 const NO_TMP_DIR = 'fileUploadErrorNoTmpDir'; 29 const CANT_WRITE = 'fileUploadErrorCantWrite'; 30 const EXTENSION = 'fileUploadErrorExtension'; 31 const ATTACK = 'fileUploadErrorAttack'; 32 const FILE_NOT_FOUND = 'fileUploadErrorFileNotFound'; 33 const UNKNOWN = 'fileUploadErrorUnknown'; 34 35 /** 36 * @var array Error message templates 37 */ 38 protected $messageTemplates = array( 39 self::INI_SIZE => "File '%value%' exceeds the defined ini size", 40 self::FORM_SIZE => "File '%value%' exceeds the defined form size", 41 self::PARTIAL => "File '%value%' was only partially uploaded", 42 self::NO_FILE => "File '%value%' was not uploaded", 43 self::NO_TMP_DIR => "No temporary directory was found for file '%value%'", 44 self::CANT_WRITE => "File '%value%' can't be written", 45 self::EXTENSION => "A PHP extension returned an error while uploading the file '%value%'", 46 self::ATTACK => "File '%value%' was illegally uploaded. This could be a possible attack", 47 self::FILE_NOT_FOUND => "File '%value%' was not found", 48 self::UNKNOWN => "Unknown error while uploading file '%value%'" 49 ); 50 51 protected $options = array( 52 'files' => array(), 53 ); 54 55 /** 56 * Sets validator options 57 * 58 * The array $files must be given in syntax of Zend\File\Transfer\Transfer to be checked 59 * If no files are given the $_FILES array will be used automatically. 60 * NOTE: This validator will only work with HTTP POST uploads! 61 * 62 * @param array|\Traversable $options Array of files in syntax of \Zend\File\Transfer\Transfer 63 */ 64 public function __construct($options = array()) 65 { 66 if (is_array($options) && !array_key_exists('files', $options)) { 67 $options = array('files' => $options); 68 } 69 70 parent::__construct($options); 71 } 72 73 /** 74 * Returns the array of set files 75 * 76 * @param string $file (Optional) The file to return in detail 77 * @return array 78 * @throws Exception\InvalidArgumentException If file is not found 79 */ 80 public function getFiles($file = null) 81 { 82 if ($file !== null) { 83 $return = array(); 84 foreach ($this->options['files'] as $name => $content) { 85 if ($name === $file) { 86 $return[$file] = $this->options['files'][$name]; 87 } 88 89 if ($content['name'] === $file) { 90 $return[$name] = $this->options['files'][$name]; 91 } 92 } 93 94 if (count($return) === 0) { 95 throw new Exception\InvalidArgumentException("The file '$file' was not found"); 96 } 97 98 return $return; 99 } 100 101 return $this->options['files']; 102 } 103 104 /** 105 * Sets the files to be checked 106 * 107 * @param array $files The files to check in syntax of \Zend\File\Transfer\Transfer 108 * @return Upload Provides a fluent interface 109 */ 110 public function setFiles($files = array()) 111 { 112 if (count($files) === 0) { 113 $this->options['files'] = $_FILES; 114 } else { 115 $this->options['files'] = $files; 116 } 117 118 if ($this->options['files'] === null) { 119 $this->options['files'] = array(); 120 } 121 122 foreach ($this->options['files'] as $file => $content) { 123 if (!isset($content['error'])) { 124 unset($this->options['files'][$file]); 125 } 126 } 127 128 return $this; 129 } 130 131 /** 132 * Returns true if and only if the file was uploaded without errors 133 * 134 * @param string $value Single file to check for upload errors, when giving null the $_FILES array 135 * from initialization will be used 136 * @param mixed $file 137 * @return bool 138 */ 139 public function isValid($value, $file = null) 140 { 141 $files = array(); 142 $this->setValue($value); 143 if (array_key_exists($value, $this->getFiles())) { 144 $files = array_merge($files, $this->getFiles($value)); 145 } else { 146 foreach ($this->getFiles() as $file => $content) { 147 if (isset($content['name']) && ($content['name'] === $value)) { 148 $files = array_merge($files, $this->getFiles($file)); 149 } 150 151 if (isset($content['tmp_name']) && ($content['tmp_name'] === $value)) { 152 $files = array_merge($files, $this->getFiles($file)); 153 } 154 } 155 } 156 157 if (empty($files)) { 158 return $this->throwError($file, self::FILE_NOT_FOUND); 159 } 160 161 foreach ($files as $file => $content) { 162 $this->value = $file; 163 switch ($content['error']) { 164 case 0: 165 if (!is_uploaded_file($content['tmp_name'])) { 166 $this->throwError($content, self::ATTACK); 167 } 168 break; 169 170 case 1: 171 $this->throwError($content, self::INI_SIZE); 172 break; 173 174 case 2: 175 $this->throwError($content, self::FORM_SIZE); 176 break; 177 178 case 3: 179 $this->throwError($content, self::PARTIAL); 180 break; 181 182 case 4: 183 $this->throwError($content, self::NO_FILE); 184 break; 185 186 case 6: 187 $this->throwError($content, self::NO_TMP_DIR); 188 break; 189 190 case 7: 191 $this->throwError($content, self::CANT_WRITE); 192 break; 193 194 case 8: 195 $this->throwError($content, self::EXTENSION); 196 break; 197 198 default: 199 $this->throwError($content, self::UNKNOWN); 200 break; 201 } 202 } 203 204 if (count($this->getMessages()) > 0) { 205 return false; 206 } 207 208 return true; 209 } 210 211 /** 212 * Throws an error of the given type 213 * 214 * @param string $file 215 * @param string $errorType 216 * @return false 217 */ 218 protected function throwError($file, $errorType) 219 { 220 if ($file !== null) { 221 if (is_array($file)) { 222 if (array_key_exists('name', $file)) { 223 $this->value = $file['name']; 224 } 225 } elseif (is_string($file)) { 226 $this->value = $file; 227 } 228 } 229 230 $this->error($errorType); 231 return false; 232 } 233} 234