• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / http / 27922157098

21 Jun 2026 11:58PM UTC coverage: 82.955% (-0.2%) from 83.128%
27922157098

push

github

dg
uses #Deprecated wip

1061 of 1279 relevant lines covered (82.96%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.29
/src/Http/FileUpload.php
1
<?php declare(strict_types=1);
1✔
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Nette\Http;
9

10
use Nette;
11
use Nette\Utils\Image;
12
use function array_intersect_key, array_map, basename, chmod, dirname, file_get_contents, filesize, finfo_file, finfo_open, getimagesize, image_type_to_extension, in_array, is_string, is_uploaded_file, preg_replace, str_replace, trim, unlink;
13

14

15
/**
16
 * Provides access to individual files that have been uploaded by a client.
17
 *
18
 * @property-read string $name
19
 * @property-read string $sanitizedName
20
 * @property-read string $untrustedFullPath
21
 * @property-read ?string $contentType
22
 * @property-read int $size
23
 * @property-read string $temporaryFile
24
 * @property-read int $error
25
 * @property-read bool $ok
26
 * @property-read ?string $contents
27
 */
28
final class FileUpload
29
{
30
        use Nette\SmartObject;
31

32
        #[\Deprecated]
33
        public const IMAGE_MIME_TYPES = ['image/gif', 'image/png', 'image/jpeg', 'image/webp'];
34

35
        private readonly string $name;
36
        private readonly ?string $fullPath;
37
        private string|false|null $type = null;
38
        private string|false|null $extension = null;
39
        private readonly int $size;
40
        private string $tmpName;
41
        private readonly int $error;
42

43

44
        /** @param array{name?: string, full_path?: string, size?: int, tmp_name?: string, error?: int, type?: string}|string|null  $value */
45
        public function __construct(array|string|null $value)
1✔
46
        {
47
                if (is_string($value)) {
1✔
48
                        $value = [
1✔
49
                                'name' => basename($value),
1✔
50
                                'full_path' => $value,
1✔
51
                                'size' => filesize($value) ?: 0,
1✔
52
                                'tmp_name' => $value,
1✔
53
                                'error' => UPLOAD_ERR_OK,
1✔
54
                        ];
55
                }
56

57
                $this->name = $value['name'] ?? '';
1✔
58
                $this->fullPath = $value['full_path'] ?? null;
1✔
59
                $this->size = $value['size'] ?? 0;
1✔
60
                $this->tmpName = $value['tmp_name'] ?? '';
1✔
61
                $this->error = $value['error'] ?? UPLOAD_ERR_NO_FILE;
1✔
62
        }
1✔
63

64

65
        #[\Deprecated('use getUntrustedName()')]
66
        public function getName(): string
67
        {
68
                trigger_error(__METHOD__ . '() is deprecated, use getUntrustedName()', E_USER_DEPRECATED);
×
69
                return $this->name;
×
70
        }
71

72

73
        /**
74
         * Returns the original file name as submitted by the browser. Do not trust the value returned by this method.
75
         * A client could send a malicious filename with the intention to corrupt or hack your application.
76
         */
77
        public function getUntrustedName(): string
78
        {
79
                return $this->name;
1✔
80
        }
81

82

83
        /**
84
         * Returns the sanitized file name. The resulting name contains only ASCII characters [a-zA-Z0-9.-].
85
         * If the name does not contain such characters, it returns 'unknown'. If the file is an image supported by PHP,
86
         * it returns the correct file extension. Do not blindly trust the value returned by this method.
87
         */
88
        public function getSanitizedName(): string
89
        {
90
                $name = Nette\Utils\Strings::webalize($this->name, '.', lower: false);
1✔
91
                $name = str_replace(['-.', '.-'], '.', $name);
1✔
92
                $name = trim($name, '.-');
1✔
93
                $name = $name === '' ? 'unknown' : $name;
1✔
94
                if ($this->isImage()) {
1✔
95
                        $name = preg_replace('#\.[^.]+$#D', '', $name);
1✔
96
                        $name .= '.' . $this->getSuggestedExtension();
1✔
97
                }
98

99
                return $name;
1✔
100
        }
101

102

103
        /**
104
         * Returns the original full path as submitted by the browser during directory upload. Do not trust the value
105
         * returned by this method. A client could send a malicious directory structure with the intention to corrupt
106
         * or hack your application.
107
         */
108
        public function getUntrustedFullPath(): string
109
        {
110
                return $this->fullPath ?? $this->name;
1✔
111
        }
112

113

114
        /**
115
         * Detects the MIME content type of the uploaded file based on its signature. Requires PHP extension fileinfo.
116
         * If the upload was not successful or the detection failed, it returns null.
117
         */
118
        public function getContentType(): ?string
119
        {
120
                if ($this->isOk()) {
1✔
121
                        $this->type ??= ($finfo = finfo_open(FILEINFO_MIME_TYPE)) ? finfo_file($finfo, $this->tmpName) : false;
1✔
122
                }
123

124
                return $this->type ?: null;
1✔
125
        }
126

127

128
        /**
129
         * Returns the appropriate file extension (without the period) corresponding to the detected MIME type. Requires the PHP extension fileinfo.
130
         */
131
        public function getSuggestedExtension(): ?string
132
        {
133
                if ($this->isOk() && $this->extension === null) {
1✔
134
                        $finfo = finfo_open(FILEINFO_EXTENSION);
1✔
135
                        $exts = $finfo ? finfo_file($finfo, $this->tmpName) : false;
1✔
136
                        if ($exts && $exts !== '???') {
1✔
137
                                return $this->extension = preg_replace('~[/,].*~', '', $exts);
1✔
138
                        }
139
                        $info = Nette\Utils\Helpers::falseToNull(@getimagesize($this->tmpName)); // @ - files smaller than 12 bytes causes read error
1✔
140
                        if ($info) {
1✔
141
                                return $this->extension = image_type_to_extension($info[2], include_dot: false) ?: null;
×
142
                        }
143
                        $this->extension = false;
1✔
144
                }
145

146
                return $this->extension ?: null;
1✔
147
        }
148

149

150
        /**
151
         * Returns the size of the uploaded file in bytes.
152
         */
153
        public function getSize(): int
154
        {
155
                return $this->size;
1✔
156
        }
157

158

159
        /**
160
         * Returns the path of the temporary location of the uploaded file.
161
         */
162
        public function getTemporaryFile(): string
163
        {
164
                return $this->tmpName;
1✔
165
        }
166

167

168
        /**
169
         * Returns the path of the temporary location of the uploaded file.
170
         */
171
        public function __toString(): string
172
        {
173
                return $this->tmpName;
1✔
174
        }
175

176

177
        /**
178
         * Returns the upload error code (one of the UPLOAD_ERR_XXX constants).
179
         * @see http://php.net/manual/en/features.file-upload.errors.php
180
         */
181
        public function getError(): int
182
        {
183
                return $this->error;
1✔
184
        }
185

186

187
        /**
188
         * Returns true if the file was uploaded successfully.
189
         */
190
        public function isOk(): bool
191
        {
192
                return $this->error === UPLOAD_ERR_OK;
1✔
193
        }
194

195

196
        /**
197
         * Returns true if the user has uploaded a file.
198
         */
199
        public function hasFile(): bool
200
        {
201
                return $this->error !== UPLOAD_ERR_NO_FILE;
1✔
202
        }
203

204

205
        /**
206
         * Moves an uploaded file to a new location. If the destination file already exists, it will be overwritten.
207
         */
208
        public function move(string $dest): static
209
        {
210
                $dir = dirname($dest);
×
211
                Nette\Utils\FileSystem::createDir($dir);
×
212
                @unlink($dest); // @ - file may not exists
×
213
                Nette\Utils\Callback::invokeSafe(
×
214
                        is_uploaded_file($this->tmpName) ? 'move_uploaded_file' : 'rename',
×
215
                        [$this->tmpName, $dest],
×
216
                        function (string $message) use ($dest): void {
×
217
                                throw new Nette\InvalidStateException("Unable to move uploaded file '$this->tmpName' to '$dest'. $message");
218
                        },
×
219
                );
220
                @chmod($dest, 0o666); // @ - possible low permission to chmod
×
221
                $this->tmpName = $dest;
×
222
                return $this;
×
223
        }
224

225

226
        /**
227
         * Checks whether the uploaded file is an image in a format supported by PHP (detectable via fileinfo, loadable via GD).
228
         * Detection is based on file signature; full integrity is not verified.
229
         */
230
        public function isImage(): bool
231
        {
232
                $types = array_map(Image::typeToMimeType(...), Image::getSupportedTypes());
1✔
233
                return in_array($this->getContentType(), $types, strict: true);
1✔
234
        }
235

236

237
        /**
238
         * Converts uploaded image to Nette\Utils\Image object.
239
         * @throws Nette\Utils\ImageException  If the upload was not successful or is not a valid image
240
         */
241
        public function toImage(): Image
242
        {
243
                return Image::fromFile($this->tmpName);
×
244
        }
245

246

247
        /**
248
         * Returns the [width, height] dimensions of the uploaded image, or null if it is not a valid image.
249
         * @return ?array{int, int}
250
         */
251
        public function getImageSize(): ?array
252
        {
253
                return $this->isImage() && ($info = getimagesize($this->tmpName))
1✔
254
                        ? array_intersect_key($info, [0, 1])
1✔
255
                        : null;
1✔
256
        }
257

258

259
        /**
260
         * Returns image file extension based on detected content type (without dot).
261
         * @deprecated use getSuggestedExtension()
262
         */
263
        public function getImageFileExtension(): ?string
264
        {
265
                return $this->getSuggestedExtension();
×
266
        }
267

268

269
        /**
270
         * Returns the contents of the uploaded file. If the upload was not successful, it returns null.
271
         */
272
        public function getContents(): ?string
273
        {
274
                if (!$this->isOk()) {
1✔
275
                        return null;
×
276
                }
277

278
                $res = file_get_contents($this->tmpName);
1✔
279
                return $res === false
1✔
280
                        ? throw new Nette\IOException("Unable to read file '$this->tmpName'.")
×
281
                        : $res;
1✔
282
        }
283
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc