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\HttpFoundation\File;
13:
14: use Symfony\Component\HttpFoundation\File\Exception\FileException;
15: use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
16: use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;
17:
18: /**
19: * A file uploaded through a form.
20: *
21: * @author Bernhard Schussek <bschussek@gmail.com>
22: * @author Florian Eckerstorfer <florian@eckerstorfer.org>
23: * @author Fabien Potencier <fabien@symfony.com>
24: *
25: * @api
26: */
27: class UploadedFile extends File
28: {
29: /**
30: * Whether the test mode is activated.
31: *
32: * Local files are used in test mode hence the code should not enforce HTTP uploads.
33: *
34: * @var bool
35: */
36: private $test = false;
37:
38: /**
39: * The original name of the uploaded file.
40: *
41: * @var string
42: */
43: private $originalName;
44:
45: /**
46: * The mime type provided by the uploader.
47: *
48: * @var string
49: */
50: private $mimeType;
51:
52: /**
53: * The file size provided by the uploader.
54: *
55: * @var string
56: */
57: private $size;
58:
59: /**
60: * The UPLOAD_ERR_XXX constant provided by the uploader.
61: *
62: * @var int
63: */
64: private $error;
65:
66: /**
67: * Accepts the information of the uploaded file as provided by the PHP global $_FILES.
68: *
69: * The file object is only created when the uploaded file is valid (i.e. when the
70: * isValid() method returns true). Otherwise the only methods that could be called
71: * on an UploadedFile instance are:
72: *
73: * * getClientOriginalName,
74: * * getClientMimeType,
75: * * isValid,
76: * * getError.
77: *
78: * Calling any other method on an non-valid instance will cause an unpredictable result.
79: *
80: * @param string $path The full temporary path to the file
81: * @param string $originalName The original file name
82: * @param string $mimeType The type of the file as provided by PHP
83: * @param int $size The file size
84: * @param int $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants)
85: * @param bool $test Whether the test mode is active
86: *
87: * @throws FileException If file_uploads is disabled
88: * @throws FileNotFoundException If the file does not exist
89: *
90: * @api
91: */
92: public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false)
93: {
94: $this->originalName = $this->getName($originalName);
95: $this->mimeType = $mimeType ?: 'application/octet-stream';
96: $this->size = $size;
97: $this->error = $error ?: UPLOAD_ERR_OK;
98: $this->test = (bool) $test;
99:
100: parent::__construct($path, UPLOAD_ERR_OK === $this->error);
101: }
102:
103: /**
104: * Returns the original file name.
105: *
106: * It is extracted from the request from which the file has been uploaded.
107: * Then it should not be considered as a safe value.
108: *
109: * @return string|null The original name
110: *
111: * @api
112: */
113: public function getClientOriginalName()
114: {
115: return $this->originalName;
116: }
117:
118: /**
119: * Returns the original file extension.
120: *
121: * It is extracted from the original file name that was uploaded.
122: * Then it should not be considered as a safe value.
123: *
124: * @return string The extension
125: */
126: public function getClientOriginalExtension()
127: {
128: return pathinfo($this->originalName, PATHINFO_EXTENSION);
129: }
130:
131: /**
132: * Returns the file mime type.
133: *
134: * The client mime type is extracted from the request from which the file
135: * was uploaded, so it should not be considered as a safe value.
136: *
137: * For a trusted mime type, use getMimeType() instead (which guesses the mime
138: * type based on the file content).
139: *
140: * @return string|null The mime type
141: *
142: * @see getMimeType()
143: *
144: * @api
145: */
146: public function getClientMimeType()
147: {
148: return $this->mimeType;
149: }
150:
151: /**
152: * Returns the extension based on the client mime type.
153: *
154: * If the mime type is unknown, returns null.
155: *
156: * This method uses the mime type as guessed by getClientMimeType()
157: * to guess the file extension. As such, the extension returned
158: * by this method cannot be trusted.
159: *
160: * For a trusted extension, use guessExtension() instead (which guesses
161: * the extension based on the guessed mime type for the file).
162: *
163: * @return string|null The guessed extension or null if it cannot be guessed
164: *
165: * @see guessExtension()
166: * @see getClientMimeType()
167: */
168: public function guessClientExtension()
169: {
170: $type = $this->getClientMimeType();
171: $guesser = ExtensionGuesser::getInstance();
172:
173: return $guesser->guess($type);
174: }
175:
176: /**
177: * Returns the file size.
178: *
179: * It is extracted from the request from which the file has been uploaded.
180: * Then it should not be considered as a safe value.
181: *
182: * @return int|null The file size
183: *
184: * @api
185: */
186: public function getClientSize()
187: {
188: return $this->size;
189: }
190:
191: /**
192: * Returns the upload error.
193: *
194: * If the upload was successful, the constant UPLOAD_ERR_OK is returned.
195: * Otherwise one of the other UPLOAD_ERR_XXX constants is returned.
196: *
197: * @return int The upload error
198: *
199: * @api
200: */
201: public function getError()
202: {
203: return $this->error;
204: }
205:
206: /**
207: * Returns whether the file was uploaded successfully.
208: *
209: * @return bool True if the file has been uploaded with HTTP and no error occurred.
210: *
211: * @api
212: */
213: public function isValid()
214: {
215: $isOk = $this->error === UPLOAD_ERR_OK;
216:
217: return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname());
218: }
219:
220: /**
221: * Moves the file to a new location.
222: *
223: * @param string $directory The destination folder
224: * @param string $name The new file name
225: *
226: * @return File A File object representing the new file
227: *
228: * @throws FileException if, for any reason, the file could not have been moved
229: *
230: * @api
231: */
232: public function move($directory, $name = null)
233: {
234: if ($this->isValid()) {
235: if ($this->test) {
236: return parent::move($directory, $name);
237: }
238:
239: $target = $this->getTargetFile($directory, $name);
240:
241: if (!@move_uploaded_file($this->getPathname(), $target)) {
242: $error = error_get_last();
243: throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message'])));
244: }
245:
246: @chmod($target, 0666 & ~umask());
247:
248: return $target;
249: }
250:
251: throw new FileException($this->getErrorMessage());
252: }
253:
254: /**
255: * Returns the maximum size of an uploaded file as configured in php.ini.
256: *
257: * @return int The maximum size of an uploaded file in bytes
258: */
259: public static function getMaxFilesize()
260: {
261: $iniMax = strtolower(ini_get('upload_max_filesize'));
262:
263: if ('' === $iniMax) {
264: return PHP_INT_MAX;
265: }
266:
267: $max = ltrim($iniMax, '+');
268: if (0 === strpos($max, '0x')) {
269: $max = intval($max, 16);
270: } elseif (0 === strpos($max, '0')) {
271: $max = intval($max, 8);
272: } else {
273: $max = intval($max);
274: }
275:
276: switch (substr($iniMax, -1)) {
277: case 't': $max *= 1024;
278: case 'g': $max *= 1024;
279: case 'm': $max *= 1024;
280: case 'k': $max *= 1024;
281: }
282:
283: return $max;
284: }
285:
286: /**
287: * Returns an informative upload error message.
288: *
289: * @return string The error message regarding the specified error code
290: */
291: public function getErrorMessage()
292: {
293: static $errors = array(
294: UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).',
295: UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.',
296: UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.',
297: UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
298: UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.',
299: UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
300: UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.',
301: );
302:
303: $errorCode = $this->error;
304: $maxFilesize = $errorCode === UPLOAD_ERR_INI_SIZE ? self::getMaxFilesize() / 1024 : 0;
305: $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.';
306:
307: return sprintf($message, $this->getClientOriginalName(), $maxFilesize);
308: }
309: }
310: