| 1 |
lars |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
/*
|
|
|
4 |
* This file is part of the symfony package.
|
|
|
5 |
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
|
|
6 |
*
|
|
|
7 |
* For the full copyright and license information, please view the LICENSE
|
|
|
8 |
* file that was distributed with this source code.
|
|
|
9 |
*/
|
|
|
10 |
|
|
|
11 |
/**
|
|
|
12 |
* sfValidatorFile validates an uploaded file.
|
|
|
13 |
*
|
|
|
14 |
* @package symfony
|
|
|
15 |
* @subpackage validator
|
|
|
16 |
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
|
|
17 |
* @version SVN: $Id: sfValidatorFile.class.php 32836 2011-07-27 07:15:58Z fabien $
|
|
|
18 |
*/
|
|
|
19 |
class sfValidatorFile extends sfValidatorBase
|
|
|
20 |
{
|
|
|
21 |
/**
|
|
|
22 |
* Configures the current validator.
|
|
|
23 |
*
|
|
|
24 |
* Available options:
|
|
|
25 |
*
|
|
|
26 |
* * max_size: The maximum file size in bytes (cannot exceed upload_max_filesize in php.ini)
|
|
|
27 |
* * mime_types: Allowed mime types array or category (available categories: web_images)
|
|
|
28 |
* * mime_type_guessers: An array of mime type guesser PHP callables (must return the mime type or null)
|
|
|
29 |
* * mime_categories: An array of mime type categories (web_images is defined by default)
|
|
|
30 |
* * path: The path where to save the file - as used by the sfValidatedFile class (optional)
|
|
|
31 |
* * validated_file_class: Name of the class that manages the cleaned uploaded file (optional)
|
|
|
32 |
*
|
|
|
33 |
* There are 3 built-in mime type guessers:
|
|
|
34 |
*
|
|
|
35 |
* * guessFromFileinfo: Uses the finfo_open() function (from the Fileinfo PECL extension)
|
|
|
36 |
* * guessFromMimeContentType: Uses the mime_content_type() function (deprecated)
|
|
|
37 |
* * guessFromFileBinary: Uses the file binary (only works on *nix system)
|
|
|
38 |
*
|
|
|
39 |
* Available error codes:
|
|
|
40 |
*
|
|
|
41 |
* * max_size
|
|
|
42 |
* * mime_types
|
|
|
43 |
* * partial
|
|
|
44 |
* * no_tmp_dir
|
|
|
45 |
* * cant_write
|
|
|
46 |
* * extension
|
|
|
47 |
*
|
|
|
48 |
* @param array $options An array of options
|
|
|
49 |
* @param array $messages An array of error messages
|
|
|
50 |
*
|
|
|
51 |
* @see sfValidatorBase
|
|
|
52 |
*/
|
|
|
53 |
protected function configure($options = array(), $messages = array())
|
|
|
54 |
{
|
|
|
55 |
if (!ini_get('file_uploads'))
|
|
|
56 |
{
|
|
|
57 |
throw new LogicException(sprintf('Unable to use a file validator as "file_uploads" is disabled in your php.ini file (%s)', get_cfg_var('cfg_file_path')));
|
|
|
58 |
}
|
|
|
59 |
|
|
|
60 |
$this->addOption('max_size');
|
|
|
61 |
$this->addOption('mime_types');
|
|
|
62 |
$this->addOption('mime_type_guessers', array(
|
|
|
63 |
array($this, 'guessFromFileinfo'),
|
|
|
64 |
array($this, 'guessFromMimeContentType'),
|
|
|
65 |
array($this, 'guessFromFileBinary'),
|
|
|
66 |
));
|
|
|
67 |
$this->addOption('mime_categories', array(
|
|
|
68 |
'web_images' => array(
|
|
|
69 |
'image/jpeg',
|
|
|
70 |
'image/pjpeg',
|
|
|
71 |
'image/png',
|
|
|
72 |
'image/x-png',
|
|
|
73 |
'image/gif',
|
|
|
74 |
)));
|
|
|
75 |
$this->addOption('validated_file_class', 'sfValidatedFile');
|
|
|
76 |
$this->addOption('path', null);
|
|
|
77 |
|
|
|
78 |
$this->addMessage('max_size', 'File is too large (maximum is %max_size% bytes).');
|
|
|
79 |
$this->addMessage('mime_types', 'Invalid mime type (%mime_type%).');
|
|
|
80 |
$this->addMessage('partial', 'The uploaded file was only partially uploaded.');
|
|
|
81 |
$this->addMessage('no_tmp_dir', 'Missing a temporary folder.');
|
|
|
82 |
$this->addMessage('cant_write', 'Failed to write file to disk.');
|
|
|
83 |
$this->addMessage('extension', 'File upload stopped by extension.');
|
|
|
84 |
}
|
|
|
85 |
|
|
|
86 |
/**
|
|
|
87 |
* This validator always returns a sfValidatedFile object.
|
|
|
88 |
*
|
|
|
89 |
* The input value must be an array with the following keys:
|
|
|
90 |
*
|
|
|
91 |
* * tmp_name: The absolute temporary path to the file
|
|
|
92 |
* * name: The original file name (optional)
|
|
|
93 |
* * type: The file content type (optional)
|
|
|
94 |
* * error: The error code (optional)
|
|
|
95 |
* * size: The file size in bytes (optional)
|
|
|
96 |
*
|
|
|
97 |
* @see sfValidatorBase
|
|
|
98 |
*/
|
|
|
99 |
protected function doClean($value)
|
|
|
100 |
{
|
|
|
101 |
if (!is_array($value) || !isset($value['tmp_name']))
|
|
|
102 |
{
|
|
|
103 |
throw new sfValidatorError($this, 'invalid', array('value' => (string) $value));
|
|
|
104 |
}
|
|
|
105 |
|
|
|
106 |
if (!isset($value['name']))
|
|
|
107 |
{
|
|
|
108 |
$value['name'] = '';
|
|
|
109 |
}
|
|
|
110 |
|
|
|
111 |
if (!isset($value['error']))
|
|
|
112 |
{
|
|
|
113 |
$value['error'] = UPLOAD_ERR_OK;
|
|
|
114 |
}
|
|
|
115 |
|
|
|
116 |
if (!isset($value['size']))
|
|
|
117 |
{
|
|
|
118 |
$value['size'] = filesize($value['tmp_name']);
|
|
|
119 |
}
|
|
|
120 |
|
|
|
121 |
if (!isset($value['type']))
|
|
|
122 |
{
|
|
|
123 |
$value['type'] = 'application/octet-stream';
|
|
|
124 |
}
|
|
|
125 |
|
|
|
126 |
switch ($value['error'])
|
|
|
127 |
{
|
|
|
128 |
case UPLOAD_ERR_INI_SIZE:
|
|
|
129 |
$max = ini_get('upload_max_filesize');
|
|
|
130 |
if ($this->getOption('max_size'))
|
|
|
131 |
{
|
|
|
132 |
$max = min($max, $this->getOption('max_size'));
|
|
|
133 |
}
|
|
|
134 |
throw new sfValidatorError($this, 'max_size', array('max_size' => $max, 'size' => (int) $value['size']));
|
|
|
135 |
case UPLOAD_ERR_FORM_SIZE:
|
|
|
136 |
throw new sfValidatorError($this, 'max_size', array('max_size' => 0, 'size' => (int) $value['size']));
|
|
|
137 |
case UPLOAD_ERR_PARTIAL:
|
|
|
138 |
throw new sfValidatorError($this, 'partial');
|
|
|
139 |
case UPLOAD_ERR_NO_TMP_DIR:
|
|
|
140 |
throw new sfValidatorError($this, 'no_tmp_dir');
|
|
|
141 |
case UPLOAD_ERR_CANT_WRITE:
|
|
|
142 |
throw new sfValidatorError($this, 'cant_write');
|
|
|
143 |
case UPLOAD_ERR_EXTENSION:
|
|
|
144 |
throw new sfValidatorError($this, 'extension');
|
|
|
145 |
}
|
|
|
146 |
|
|
|
147 |
// check file size
|
|
|
148 |
if ($this->hasOption('max_size') && $this->getOption('max_size') < (int) $value['size'])
|
|
|
149 |
{
|
|
|
150 |
throw new sfValidatorError($this, 'max_size', array('max_size' => $this->getOption('max_size'), 'size' => (int) $value['size']));
|
|
|
151 |
}
|
|
|
152 |
|
|
|
153 |
$mimeType = $this->getMimeType((string) $value['tmp_name'], (string) $value['type']);
|
|
|
154 |
|
|
|
155 |
// check mime type
|
|
|
156 |
if ($this->hasOption('mime_types'))
|
|
|
157 |
{
|
|
|
158 |
$mimeTypes = is_array($this->getOption('mime_types')) ? $this->getOption('mime_types') : $this->getMimeTypesFromCategory($this->getOption('mime_types'));
|
|
|
159 |
if (!in_array($mimeType, array_map('strtolower', $mimeTypes)))
|
|
|
160 |
{
|
|
|
161 |
throw new sfValidatorError($this, 'mime_types', array('mime_types' => $mimeTypes, 'mime_type' => $mimeType));
|
|
|
162 |
}
|
|
|
163 |
}
|
|
|
164 |
|
|
|
165 |
$class = $this->getOption('validated_file_class');
|
|
|
166 |
|
|
|
167 |
return new $class($value['name'], $mimeType, $value['tmp_name'], $value['size'], $this->getOption('path'));
|
|
|
168 |
}
|
|
|
169 |
|
|
|
170 |
/**
|
|
|
171 |
* Returns the mime type of a file.
|
|
|
172 |
*
|
|
|
173 |
* This methods call each mime_type_guessers option callables to
|
|
|
174 |
* guess the mime type.
|
|
|
175 |
*
|
|
|
176 |
* This method always returns a lower-cased string as mime types are case-insensitive
|
|
|
177 |
* as per the RFC 2616 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7).
|
|
|
178 |
*
|
|
|
179 |
* @param string $file The absolute path of a file
|
|
|
180 |
* @param string $fallback The default mime type to return if not guessable
|
|
|
181 |
*
|
|
|
182 |
* @return string The mime type of the file (fallback is returned if not guessable)
|
|
|
183 |
*/
|
|
|
184 |
protected function getMimeType($file, $fallback)
|
|
|
185 |
{
|
|
|
186 |
foreach ($this->getOption('mime_type_guessers') as $method)
|
|
|
187 |
{
|
|
|
188 |
$type = call_user_func($method, $file);
|
|
|
189 |
|
|
|
190 |
if (null !== $type && $type !== false)
|
|
|
191 |
{
|
|
|
192 |
return strtolower($type);
|
|
|
193 |
}
|
|
|
194 |
}
|
|
|
195 |
|
|
|
196 |
return strtolower($fallback);
|
|
|
197 |
}
|
|
|
198 |
|
|
|
199 |
/**
|
|
|
200 |
* Guess the file mime type with PECL Fileinfo extension
|
|
|
201 |
*
|
|
|
202 |
* @param string $file The absolute path of a file
|
|
|
203 |
*
|
|
|
204 |
* @return string The mime type of the file (null if not guessable)
|
|
|
205 |
*/
|
|
|
206 |
protected function guessFromFileinfo($file)
|
|
|
207 |
{
|
|
|
208 |
if (!function_exists('finfo_open') || !is_readable($file))
|
|
|
209 |
{
|
|
|
210 |
return null;
|
|
|
211 |
}
|
|
|
212 |
|
|
|
213 |
if (!$finfo = new finfo(FILEINFO_MIME))
|
|
|
214 |
{
|
|
|
215 |
return null;
|
|
|
216 |
}
|
|
|
217 |
|
|
|
218 |
$type = $finfo->file($file);
|
|
|
219 |
|
|
|
220 |
// remove charset (added as of PHP 5.3)
|
|
|
221 |
if (false !== $pos = strpos($type, ';'))
|
|
|
222 |
{
|
|
|
223 |
$type = substr($type, 0, $pos);
|
|
|
224 |
}
|
|
|
225 |
|
|
|
226 |
return $type;
|
|
|
227 |
}
|
|
|
228 |
|
|
|
229 |
/**
|
|
|
230 |
* Guess the file mime type with mime_content_type function (deprecated)
|
|
|
231 |
*
|
|
|
232 |
* @param string $file The absolute path of a file
|
|
|
233 |
*
|
|
|
234 |
* @return string The mime type of the file (null if not guessable)
|
|
|
235 |
*/
|
|
|
236 |
protected function guessFromMimeContentType($file)
|
|
|
237 |
{
|
|
|
238 |
if (!function_exists('mime_content_type') || !is_readable($file))
|
|
|
239 |
{
|
|
|
240 |
return null;
|
|
|
241 |
}
|
|
|
242 |
|
|
|
243 |
return mime_content_type($file);
|
|
|
244 |
}
|
|
|
245 |
|
|
|
246 |
/**
|
|
|
247 |
* Guess the file mime type with the file binary (only available on *nix)
|
|
|
248 |
*
|
|
|
249 |
* @param string $file The absolute path of a file
|
|
|
250 |
*
|
|
|
251 |
* @return string The mime type of the file (null if not guessable)
|
|
|
252 |
*/
|
|
|
253 |
protected function guessFromFileBinary($file)
|
|
|
254 |
{
|
|
|
255 |
ob_start();
|
|
|
256 |
//need to use --mime instead of -i. see #6641
|
|
|
257 |
passthru(sprintf('file -b --mime %s 2>/dev/null', escapeshellarg($file)), $return);
|
|
|
258 |
if ($return > 0)
|
|
|
259 |
{
|
|
|
260 |
ob_end_clean();
|
|
|
261 |
|
|
|
262 |
return null;
|
|
|
263 |
}
|
|
|
264 |
$type = trim(ob_get_clean());
|
|
|
265 |
|
|
|
266 |
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-.]+)#i', $type, $match))
|
|
|
267 |
{
|
|
|
268 |
// it's not a type, but an error message
|
|
|
269 |
return null;
|
|
|
270 |
}
|
|
|
271 |
|
|
|
272 |
return $match[1];
|
|
|
273 |
}
|
|
|
274 |
|
|
|
275 |
protected function getMimeTypesFromCategory($category)
|
|
|
276 |
{
|
|
|
277 |
$categories = $this->getOption('mime_categories');
|
|
|
278 |
|
|
|
279 |
if (!isset($categories[$category]))
|
|
|
280 |
{
|
|
|
281 |
throw new InvalidArgumentException(sprintf('Invalid mime type category "%s".', $category));
|
|
|
282 |
}
|
|
|
283 |
|
|
|
284 |
return $categories[$category];
|
|
|
285 |
}
|
|
|
286 |
|
|
|
287 |
/**
|
|
|
288 |
* @see sfValidatorBase
|
|
|
289 |
*/
|
|
|
290 |
protected function isEmpty($value)
|
|
|
291 |
{
|
|
|
292 |
// empty if the value is not an array
|
|
|
293 |
// or if the value comes from PHP with an error of UPLOAD_ERR_NO_FILE
|
|
|
294 |
return
|
|
|
295 |
(!is_array($value))
|
|
|
296 |
||
|
|
|
297 |
(is_array($value) && isset($value['error']) && UPLOAD_ERR_NO_FILE === $value['error']);
|
|
|
298 |
}
|
|
|
299 |
}
|