Version 1
[yaffs-website] / vendor / symfony / validator / Constraints / FileValidator.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Validator\Constraints;
13
14 use Symfony\Component\HttpFoundation\File\File as FileObject;
15 use Symfony\Component\HttpFoundation\File\UploadedFile;
16 use Symfony\Component\Validator\Context\ExecutionContextInterface;
17 use Symfony\Component\Validator\Constraint;
18 use Symfony\Component\Validator\ConstraintValidator;
19 use Symfony\Component\Validator\Exception\UnexpectedTypeException;
20
21 /**
22  * @author Bernhard Schussek <bschussek@gmail.com>
23  */
24 class FileValidator extends ConstraintValidator
25 {
26     const KB_BYTES = 1000;
27     const MB_BYTES = 1000000;
28     const KIB_BYTES = 1024;
29     const MIB_BYTES = 1048576;
30
31     private static $suffices = array(
32         1 => 'bytes',
33         self::KB_BYTES => 'kB',
34         self::MB_BYTES => 'MB',
35         self::KIB_BYTES => 'KiB',
36         self::MIB_BYTES => 'MiB',
37     );
38
39     /**
40      * {@inheritdoc}
41      */
42     public function validate($value, Constraint $constraint)
43     {
44         if (!$constraint instanceof File) {
45             throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\File');
46         }
47
48         if (null === $value || '' === $value) {
49             return;
50         }
51
52         if ($value instanceof UploadedFile && !$value->isValid()) {
53             switch ($value->getError()) {
54                 case UPLOAD_ERR_INI_SIZE:
55                     $iniLimitSize = UploadedFile::getMaxFilesize();
56                     if ($constraint->maxSize && $constraint->maxSize < $iniLimitSize) {
57                         $limitInBytes = $constraint->maxSize;
58                         $binaryFormat = $constraint->binaryFormat;
59                     } else {
60                         $limitInBytes = $iniLimitSize;
61                         $binaryFormat = true;
62                     }
63
64                     list($sizeAsString, $limitAsString, $suffix) = $this->factorizeSizes(0, $limitInBytes, $binaryFormat);
65                     if ($this->context instanceof ExecutionContextInterface) {
66                         $this->context->buildViolation($constraint->uploadIniSizeErrorMessage)
67                             ->setParameter('{{ limit }}', $limitAsString)
68                             ->setParameter('{{ suffix }}', $suffix)
69                             ->setCode(UPLOAD_ERR_INI_SIZE)
70                             ->addViolation();
71                     } else {
72                         $this->buildViolation($constraint->uploadIniSizeErrorMessage)
73                             ->setParameter('{{ limit }}', $limitAsString)
74                             ->setParameter('{{ suffix }}', $suffix)
75                             ->setCode(UPLOAD_ERR_INI_SIZE)
76                             ->addViolation();
77                     }
78
79                     return;
80                 case UPLOAD_ERR_FORM_SIZE:
81                     if ($this->context instanceof ExecutionContextInterface) {
82                         $this->context->buildViolation($constraint->uploadFormSizeErrorMessage)
83                             ->setCode(UPLOAD_ERR_FORM_SIZE)
84                             ->addViolation();
85                     } else {
86                         $this->buildViolation($constraint->uploadFormSizeErrorMessage)
87                             ->setCode(UPLOAD_ERR_FORM_SIZE)
88                             ->addViolation();
89                     }
90
91                     return;
92                 case UPLOAD_ERR_PARTIAL:
93                     if ($this->context instanceof ExecutionContextInterface) {
94                         $this->context->buildViolation($constraint->uploadPartialErrorMessage)
95                             ->setCode(UPLOAD_ERR_PARTIAL)
96                             ->addViolation();
97                     } else {
98                         $this->buildViolation($constraint->uploadPartialErrorMessage)
99                             ->setCode(UPLOAD_ERR_PARTIAL)
100                             ->addViolation();
101                     }
102
103                     return;
104                 case UPLOAD_ERR_NO_FILE:
105                     if ($this->context instanceof ExecutionContextInterface) {
106                         $this->context->buildViolation($constraint->uploadNoFileErrorMessage)
107                             ->setCode(UPLOAD_ERR_NO_FILE)
108                             ->addViolation();
109                     } else {
110                         $this->buildViolation($constraint->uploadNoFileErrorMessage)
111                             ->setCode(UPLOAD_ERR_NO_FILE)
112                             ->addViolation();
113                     }
114
115                     return;
116                 case UPLOAD_ERR_NO_TMP_DIR:
117                     if ($this->context instanceof ExecutionContextInterface) {
118                         $this->context->buildViolation($constraint->uploadNoTmpDirErrorMessage)
119                             ->setCode(UPLOAD_ERR_NO_TMP_DIR)
120                             ->addViolation();
121                     } else {
122                         $this->buildViolation($constraint->uploadNoTmpDirErrorMessage)
123                             ->setCode(UPLOAD_ERR_NO_TMP_DIR)
124                             ->addViolation();
125                     }
126
127                     return;
128                 case UPLOAD_ERR_CANT_WRITE:
129                     if ($this->context instanceof ExecutionContextInterface) {
130                         $this->context->buildViolation($constraint->uploadCantWriteErrorMessage)
131                             ->setCode(UPLOAD_ERR_CANT_WRITE)
132                             ->addViolation();
133                     } else {
134                         $this->buildViolation($constraint->uploadCantWriteErrorMessage)
135                             ->setCode(UPLOAD_ERR_CANT_WRITE)
136                             ->addViolation();
137                     }
138
139                     return;
140                 case UPLOAD_ERR_EXTENSION:
141                     if ($this->context instanceof ExecutionContextInterface) {
142                         $this->context->buildViolation($constraint->uploadExtensionErrorMessage)
143                             ->setCode(UPLOAD_ERR_EXTENSION)
144                             ->addViolation();
145                     } else {
146                         $this->buildViolation($constraint->uploadExtensionErrorMessage)
147                             ->setCode(UPLOAD_ERR_EXTENSION)
148                             ->addViolation();
149                     }
150
151                     return;
152                 default:
153                     if ($this->context instanceof ExecutionContextInterface) {
154                         $this->context->buildViolation($constraint->uploadErrorMessage)
155                             ->setCode($value->getError())
156                             ->addViolation();
157                     } else {
158                         $this->buildViolation($constraint->uploadErrorMessage)
159                             ->setCode($value->getError())
160                             ->addViolation();
161                     }
162
163                     return;
164             }
165         }
166
167         if (!is_scalar($value) && !$value instanceof FileObject && !(is_object($value) && method_exists($value, '__toString'))) {
168             throw new UnexpectedTypeException($value, 'string');
169         }
170
171         $path = $value instanceof FileObject ? $value->getPathname() : (string) $value;
172
173         if (!is_file($path)) {
174             if ($this->context instanceof ExecutionContextInterface) {
175                 $this->context->buildViolation($constraint->notFoundMessage)
176                     ->setParameter('{{ file }}', $this->formatValue($path))
177                     ->setCode(File::NOT_FOUND_ERROR)
178                     ->addViolation();
179             } else {
180                 $this->buildViolation($constraint->notFoundMessage)
181                     ->setParameter('{{ file }}', $this->formatValue($path))
182                     ->setCode(File::NOT_FOUND_ERROR)
183                     ->addViolation();
184             }
185
186             return;
187         }
188
189         if (!is_readable($path)) {
190             if ($this->context instanceof ExecutionContextInterface) {
191                 $this->context->buildViolation($constraint->notReadableMessage)
192                     ->setParameter('{{ file }}', $this->formatValue($path))
193                     ->setCode(File::NOT_READABLE_ERROR)
194                     ->addViolation();
195             } else {
196                 $this->buildViolation($constraint->notReadableMessage)
197                     ->setParameter('{{ file }}', $this->formatValue($path))
198                     ->setCode(File::NOT_READABLE_ERROR)
199                     ->addViolation();
200             }
201
202             return;
203         }
204
205         $sizeInBytes = filesize($path);
206
207         if (0 === $sizeInBytes) {
208             if ($this->context instanceof ExecutionContextInterface) {
209                 $this->context->buildViolation($constraint->disallowEmptyMessage)
210                     ->setParameter('{{ file }}', $this->formatValue($path))
211                     ->setCode(File::EMPTY_ERROR)
212                     ->addViolation();
213             } else {
214                 $this->buildViolation($constraint->disallowEmptyMessage)
215                     ->setParameter('{{ file }}', $this->formatValue($path))
216                     ->setCode(File::EMPTY_ERROR)
217                     ->addViolation();
218             }
219
220             return;
221         }
222
223         if ($constraint->maxSize) {
224             $limitInBytes = $constraint->maxSize;
225
226             if ($sizeInBytes > $limitInBytes) {
227                 list($sizeAsString, $limitAsString, $suffix) = $this->factorizeSizes($sizeInBytes, $limitInBytes, $constraint->binaryFormat);
228                 if ($this->context instanceof ExecutionContextInterface) {
229                     $this->context->buildViolation($constraint->maxSizeMessage)
230                         ->setParameter('{{ file }}', $this->formatValue($path))
231                         ->setParameter('{{ size }}', $sizeAsString)
232                         ->setParameter('{{ limit }}', $limitAsString)
233                         ->setParameter('{{ suffix }}', $suffix)
234                         ->setCode(File::TOO_LARGE_ERROR)
235                         ->addViolation();
236                 } else {
237                     $this->buildViolation($constraint->maxSizeMessage)
238                         ->setParameter('{{ file }}', $this->formatValue($path))
239                         ->setParameter('{{ size }}', $sizeAsString)
240                         ->setParameter('{{ limit }}', $limitAsString)
241                         ->setParameter('{{ suffix }}', $suffix)
242                         ->setCode(File::TOO_LARGE_ERROR)
243                         ->addViolation();
244                 }
245
246                 return;
247             }
248         }
249
250         if ($constraint->mimeTypes) {
251             if (!$value instanceof FileObject) {
252                 $value = new FileObject($value);
253             }
254
255             $mimeTypes = (array) $constraint->mimeTypes;
256             $mime = $value->getMimeType();
257
258             foreach ($mimeTypes as $mimeType) {
259                 if ($mimeType === $mime) {
260                     return;
261                 }
262
263                 if ($discrete = strstr($mimeType, '/*', true)) {
264                     if (strstr($mime, '/', true) === $discrete) {
265                         return;
266                     }
267                 }
268             }
269
270             if ($this->context instanceof ExecutionContextInterface) {
271                 $this->context->buildViolation($constraint->mimeTypesMessage)
272                     ->setParameter('{{ file }}', $this->formatValue($path))
273                     ->setParameter('{{ type }}', $this->formatValue($mime))
274                     ->setParameter('{{ types }}', $this->formatValues($mimeTypes))
275                     ->setCode(File::INVALID_MIME_TYPE_ERROR)
276                     ->addViolation();
277             } else {
278                 $this->buildViolation($constraint->mimeTypesMessage)
279                     ->setParameter('{{ file }}', $this->formatValue($path))
280                     ->setParameter('{{ type }}', $this->formatValue($mime))
281                     ->setParameter('{{ types }}', $this->formatValues($mimeTypes))
282                     ->setCode(File::INVALID_MIME_TYPE_ERROR)
283                     ->addViolation();
284             }
285         }
286     }
287
288     private static function moreDecimalsThan($double, $numberOfDecimals)
289     {
290         return strlen((string) $double) > strlen(round($double, $numberOfDecimals));
291     }
292
293     /**
294      * Convert the limit to the smallest possible number
295      * (i.e. try "MB", then "kB", then "bytes").
296      */
297     private function factorizeSizes($size, $limit, $binaryFormat)
298     {
299         if ($binaryFormat) {
300             $coef = self::MIB_BYTES;
301             $coefFactor = self::KIB_BYTES;
302         } else {
303             $coef = self::MB_BYTES;
304             $coefFactor = self::KB_BYTES;
305         }
306
307         $limitAsString = (string) ($limit / $coef);
308
309         // Restrict the limit to 2 decimals (without rounding! we
310         // need the precise value)
311         while (self::moreDecimalsThan($limitAsString, 2)) {
312             $coef /= $coefFactor;
313             $limitAsString = (string) ($limit / $coef);
314         }
315
316         // Convert size to the same measure, but round to 2 decimals
317         $sizeAsString = (string) round($size / $coef, 2);
318
319         // If the size and limit produce the same string output
320         // (due to rounding), reduce the coefficient
321         while ($sizeAsString === $limitAsString) {
322             $coef /= $coefFactor;
323             $limitAsString = (string) ($limit / $coef);
324             $sizeAsString = (string) round($size / $coef, 2);
325         }
326
327         return array($sizeAsString, $limitAsString, self::$suffices[$coef]);
328     }
329 }