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\Stdlib\ErrorHandler; 13use Zend\Validator\AbstractValidator; 14use Zend\Validator\Exception; 15 16/** 17 * Validator for the maximum size of a file up to a max of 2GB 18 */ 19class Size extends AbstractValidator 20{ 21 /** 22 * @const string Error constants 23 */ 24 const TOO_BIG = 'fileSizeTooBig'; 25 const TOO_SMALL = 'fileSizeTooSmall'; 26 const NOT_FOUND = 'fileSizeNotFound'; 27 28 /** 29 * @var array Error message templates 30 */ 31 protected $messageTemplates = array( 32 self::TOO_BIG => "Maximum allowed size for file is '%max%' but '%size%' detected", 33 self::TOO_SMALL => "Minimum expected size for file is '%min%' but '%size%' detected", 34 self::NOT_FOUND => "File is not readable or does not exist", 35 ); 36 37 /** 38 * @var array Error message template variables 39 */ 40 protected $messageVariables = array( 41 'min' => array('options' => 'min'), 42 'max' => array('options' => 'max'), 43 'size' => 'size', 44 ); 45 46 /** 47 * Detected size 48 * 49 * @var int 50 */ 51 protected $size; 52 53 /** 54 * Options for this validator 55 * 56 * @var array 57 */ 58 protected $options = array( 59 'min' => null, // Minimum file size, if null there is no minimum 60 'max' => null, // Maximum file size, if null there is no maximum 61 'useByteString' => true, // Use byte string? 62 ); 63 64 /** 65 * Sets validator options 66 * 67 * If $options is an integer, it will be used as maximum file size 68 * As Array is accepts the following keys: 69 * 'min': Minimum file size 70 * 'max': Maximum file size 71 * 'useByteString': Use bytestring or real size for messages 72 * 73 * @param int|array|\Traversable $options Options for the adapter 74 */ 75 public function __construct($options = null) 76 { 77 if (is_string($options) || is_numeric($options)) { 78 $options = array('max' => $options); 79 } 80 81 if (1 < func_num_args()) { 82 $argv = func_get_args(); 83 array_shift($argv); 84 $options['max'] = array_shift($argv); 85 if (!empty($argv)) { 86 $options['useByteString'] = array_shift($argv); 87 } 88 } 89 90 parent::__construct($options); 91 } 92 93 /** 94 * Should messages return bytes as integer or as string in SI notation 95 * 96 * @param bool $byteString Use bytestring ? 97 * @return int 98 */ 99 public function useByteString($byteString = true) 100 { 101 $this->options['useByteString'] = (bool) $byteString; 102 return $this; 103 } 104 105 /** 106 * Will bytestring be used? 107 * 108 * @return bool 109 */ 110 public function getByteString() 111 { 112 return $this->options['useByteString']; 113 } 114 115 /** 116 * Returns the minimum file size 117 * 118 * @param bool $raw Whether or not to force return of the raw value (defaults off) 119 * @return int|string 120 */ 121 public function getMin($raw = false) 122 { 123 $min = $this->options['min']; 124 if (!$raw && $this->getByteString()) { 125 $min = $this->toByteString($min); 126 } 127 128 return $min; 129 } 130 131 /** 132 * Sets the minimum file size 133 * 134 * File size can be an integer or a byte string 135 * This includes 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' 136 * For example: 2000, 2MB, 0.2GB 137 * 138 * @param int|string $min The minimum file size 139 * @return Size Provides a fluent interface 140 * @throws Exception\InvalidArgumentException When min is greater than max 141 */ 142 public function setMin($min) 143 { 144 if (!is_string($min) and !is_numeric($min)) { 145 throw new Exception\InvalidArgumentException('Invalid options to validator provided'); 146 } 147 148 $min = (int) $this->fromByteString($min); 149 $max = $this->getMax(true); 150 if (($max !== null) && ($min > $max)) { 151 throw new Exception\InvalidArgumentException( 152 "The minimum must be less than or equal to the maximum file size, but $min > $max" 153 ); 154 } 155 156 $this->options['min'] = $min; 157 return $this; 158 } 159 160 /** 161 * Returns the maximum file size 162 * 163 * @param bool $raw Whether or not to force return of the raw value (defaults off) 164 * @return int|string 165 */ 166 public function getMax($raw = false) 167 { 168 $max = $this->options['max']; 169 if (!$raw && $this->getByteString()) { 170 $max = $this->toByteString($max); 171 } 172 173 return $max; 174 } 175 176 /** 177 * Sets the maximum file size 178 * 179 * File size can be an integer or a byte string 180 * This includes 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' 181 * For example: 2000, 2MB, 0.2GB 182 * 183 * @param int|string $max The maximum file size 184 * @return Size Provides a fluent interface 185 * @throws Exception\InvalidArgumentException When max is smaller than min 186 */ 187 public function setMax($max) 188 { 189 if (!is_string($max) && !is_numeric($max)) { 190 throw new Exception\InvalidArgumentException('Invalid options to validator provided'); 191 } 192 193 $max = (int) $this->fromByteString($max); 194 $min = $this->getMin(true); 195 if (($min !== null) && ($max < $min)) { 196 throw new Exception\InvalidArgumentException( 197 "The maximum must be greater than or equal to the minimum file size, but $max < $min" 198 ); 199 } 200 201 $this->options['max'] = $max; 202 return $this; 203 } 204 205 /** 206 * Retrieve current detected file size 207 * 208 * @return int 209 */ 210 protected function getSize() 211 { 212 return $this->size; 213 } 214 215 /** 216 * Set current size 217 * 218 * @param int $size 219 * @return Size 220 */ 221 protected function setSize($size) 222 { 223 $this->size = $size; 224 return $this; 225 } 226 227 /** 228 * Returns true if and only if the file size of $value is at least min and 229 * not bigger than max (when max is not null). 230 * 231 * @param string|array $value File to check for size 232 * @param array $file File data from \Zend\File\Transfer\Transfer (optional) 233 * @return bool 234 */ 235 public function isValid($value, $file = null) 236 { 237 if (is_string($value) && is_array($file)) { 238 // Legacy Zend\Transfer API support 239 $filename = $file['name']; 240 $file = $file['tmp_name']; 241 } elseif (is_array($value)) { 242 if (!isset($value['tmp_name']) || !isset($value['name'])) { 243 throw new Exception\InvalidArgumentException( 244 'Value array must be in $_FILES format' 245 ); 246 } 247 $file = $value['tmp_name']; 248 $filename = $value['name']; 249 } else { 250 $file = $value; 251 $filename = basename($file); 252 } 253 $this->setValue($filename); 254 255 // Is file readable ? 256 if (empty($file) || false === stream_resolve_include_path($file)) { 257 $this->error(self::NOT_FOUND); 258 return false; 259 } 260 261 // limited to 4GB files 262 ErrorHandler::start(); 263 $size = sprintf("%u", filesize($file)); 264 ErrorHandler::stop(); 265 $this->size = $size; 266 267 // Check to see if it's smaller than min size 268 $min = $this->getMin(true); 269 $max = $this->getMax(true); 270 if (($min !== null) && ($size < $min)) { 271 if ($this->getByteString()) { 272 $this->options['min'] = $this->toByteString($min); 273 $this->size = $this->toByteString($size); 274 $this->error(self::TOO_SMALL); 275 $this->options['min'] = $min; 276 $this->size = $size; 277 } else { 278 $this->error(self::TOO_SMALL); 279 } 280 } 281 282 // Check to see if it's larger than max size 283 if (($max !== null) && ($max < $size)) { 284 if ($this->getByteString()) { 285 $this->options['max'] = $this->toByteString($max); 286 $this->size = $this->toByteString($size); 287 $this->error(self::TOO_BIG); 288 $this->options['max'] = $max; 289 $this->size = $size; 290 } else { 291 $this->error(self::TOO_BIG); 292 } 293 } 294 295 if (count($this->getMessages()) > 0) { 296 return false; 297 } 298 299 return true; 300 } 301 302 /** 303 * Returns the formatted size 304 * 305 * @param int $size 306 * @return string 307 */ 308 protected function toByteString($size) 309 { 310 $sizes = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'); 311 for ($i=0; $size >= 1024 && $i < 9; $i++) { 312 $size /= 1024; 313 } 314 315 return round($size, 2) . $sizes[$i]; 316 } 317 318 /** 319 * Returns the unformatted size 320 * 321 * @param string $size 322 * @return int 323 */ 324 protected function fromByteString($size) 325 { 326 if (is_numeric($size)) { 327 return (int) $size; 328 } 329 330 $type = trim(substr($size, -2, 1)); 331 332 $value = substr($size, 0, -1); 333 if (!is_numeric($value)) { 334 $value = substr($value, 0, -1); 335 } 336 337 switch (strtoupper($type)) { 338 case 'Y': 339 $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024); 340 break; 341 case 'Z': 342 $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024); 343 break; 344 case 'E': 345 $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024); 346 break; 347 case 'P': 348 $value *= (1024 * 1024 * 1024 * 1024 * 1024); 349 break; 350 case 'T': 351 $value *= (1024 * 1024 * 1024 * 1024); 352 break; 353 case 'G': 354 $value *= (1024 * 1024 * 1024); 355 break; 356 case 'M': 357 $value *= (1024 * 1024); 358 break; 359 case 'K': 360 $value *= 1024; 361 break; 362 default: 363 break; 364 } 365 366 return $value; 367 } 368} 369