1 | <?php |
||||||
2 | |||||||
3 | /* |
||||||
4 | * This file is part of the Koded package. |
||||||
5 | * |
||||||
6 | * (c) Mihail Binev <[email protected]> |
||||||
7 | * |
||||||
8 | * Please view the LICENSE distributed with this source code |
||||||
9 | * for the full copyright and license information. |
||||||
10 | * |
||||||
11 | */ |
||||||
12 | |||||||
13 | namespace Koded\Http; |
||||||
14 | |||||||
15 | use Koded\Exceptions\KodedException; |
||||||
16 | use Psr\Http\Message\{StreamInterface, UploadedFileInterface}; |
||||||
17 | use function Koded\Stdlib\randomstring; |
||||||
18 | |||||||
19 | |||||||
20 | class UploadedFile implements UploadedFileInterface |
||||||
21 | { |
||||||
22 | private ?string $file; |
||||||
23 | private ?string $name; |
||||||
24 | private ?string $type; |
||||||
25 | private ?int $size; |
||||||
26 | private int $error; |
||||||
27 | private bool $moved = false; |
||||||
28 | |||||||
29 | public function __construct(array $uploadedFile) |
||||||
30 | { |
||||||
31 | $this->size = $uploadedFile['size'] ?? null; |
||||||
32 | $this->file = $uploadedFile['tmp_name'] ?? null; |
||||||
33 | $this->name = $uploadedFile['name'] ?? randomstring(9); |
||||||
34 | $this->error = (int)($uploadedFile['error'] ?? \UPLOAD_ERR_OK); |
||||||
35 | |||||||
36 | // Create a file out of the stream |
||||||
37 | if ($this->file instanceof StreamInterface) { |
||||||
38 | $file = \sys_get_temp_dir() . '/' . $this->name; |
||||||
39 | \file_put_contents($file, $this->file->getContents()); |
||||||
0 ignored issues
–
show
|
|||||||
40 | $this->file = $file; |
||||||
41 | } elseif (false === \is_string($this->file)) { |
||||||
42 | throw UploadedFileException::fileNotSupported(); |
||||||
43 | 36 | } elseif (0 === \strlen($this->file)) { |
|||||
0 ignored issues
–
show
$this->file of type null is incompatible with the type string expected by parameter $string of strlen() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
44 | throw UploadedFileException::filenameCannotBeEmpty(); |
||||||
45 | 36 | } |
|||||
46 | 36 | // Never trust the provided mime type |
|||||
47 | 36 | $this->type = $this->getClientMediaType(); |
|||||
48 | 36 | } |
|||||
49 | |||||||
50 | public function getStream(): StreamInterface |
||||||
51 | 36 | { |
|||||
52 | 1 | if ($this->moved) { |
|||||
53 | 1 | throw UploadedFileException::streamNotAvailable(); |
|||||
54 | 1 | } |
|||||
55 | 35 | return new FileStream($this->file, 'w+b'); |
|||||
0 ignored issues
–
show
It seems like
$this->file can also be of type null ; however, parameter $filename of Koded\Http\FileStream::__construct() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
56 | 8 | } |
|||||
57 | 34 | ||||||
58 | 1 | public function moveTo($targetPath) |
|||||
59 | { |
||||||
60 | $this->assertUploadError(); |
||||||
61 | $this->assertTargetPath($targetPath); |
||||||
62 | 35 | // @codeCoverageIgnoreStart |
|||||
63 | 35 | try { |
|||||
64 | $this->moved = ('cli' === \php_sapi_name()) |
||||||
65 | ? \rename($this->file, $targetPath) |
||||||
0 ignored issues
–
show
It seems like
$this->file can also be of type null ; however, parameter $from of rename() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
66 | 4 | : \move_uploaded_file($this->file, $targetPath); |
|||||
0 ignored issues
–
show
It seems like
$this->file can also be of type null ; however, parameter $from of move_uploaded_file() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
67 | |||||||
68 | 4 | @\unlink($this->file); |
|||||
0 ignored issues
–
show
It seems like
$this->file can also be of type null ; however, parameter $filename of unlink() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() It seems like you do not handle an error condition for
unlink() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||||
69 | 2 | } catch (\Throwable $e) { |
|||||
70 | throw new \RuntimeException($e->getMessage()); |
||||||
71 | } |
||||||
72 | 2 | // @codeCoverageIgnoreEnd |
|||||
73 | } |
||||||
74 | |||||||
75 | public function getSize(): ?int |
||||||
76 | 14 | { |
|||||
77 | return $this->size; |
||||||
78 | 14 | } |
|||||
79 | 13 | ||||||
80 | public function getError(): int |
||||||
81 | { |
||||||
82 | return $this->error; |
||||||
83 | } |
||||||
84 | |||||||
85 | public function getClientFilename(): ?string |
||||||
86 | { |
||||||
87 | return $this->name; |
||||||
88 | } |
||||||
89 | |||||||
90 | public function getClientMediaType(): ?string |
||||||
91 | { |
||||||
92 | 6 | try { |
|||||
93 | return @(new \finfo(\FILEINFO_MIME_TYPE))->file($this->file); |
||||||
94 | } catch (\Throwable) { |
||||||
95 | 2 | return $this->type; |
|||||
96 | } |
||||||
97 | 2 | } |
|||||
98 | |||||||
99 | private function assertUploadError(): void |
||||||
100 | { |
||||||
101 | 2 | if ($this->error !== \UPLOAD_ERR_OK) { |
|||||
102 | throw new UploadedFileException($this->error); |
||||||
103 | 2 | } |
|||||
104 | } |
||||||
105 | |||||||
106 | private function assertTargetPath($targetPath): void |
||||||
107 | 2 | { |
|||||
108 | if ($this->moved) { |
||||||
109 | 2 | throw UploadedFileException::fileAlreadyMoved(); |
|||||
110 | } |
||||||
111 | if (false === \is_string($targetPath) || 0 === \mb_strlen($targetPath)) { |
||||||
112 | throw UploadedFileException::targetPathIsInvalid(); |
||||||
113 | 35 | } |
|||||
114 | if (false === \is_dir($dirname = \dirname($targetPath))) { |
||||||
115 | @\mkdir($dirname, 0777, true); |
||||||
0 ignored issues
–
show
It seems like you do not handle an error condition for
mkdir() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||||
116 | 35 | } |
|||||
117 | 1 | } |
|||||
118 | 1 | } |
|||||
119 | |||||||
120 | |||||||
121 | class UploadedFileException extends KodedException |
||||||
122 | { |
||||||
123 | 14 | protected array $messages = [ |
|||||
124 | \UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the "upload_max_filesize" directive in php.ini', |
||||||
125 | 14 | \UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the "MAX_FILE_SIZE" directive that was specified in the HTML form', |
|||||
126 | 1 | \UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded', |
|||||
127 | \UPLOAD_ERR_NO_FILE => 'No file was uploaded', |
||||||
128 | 13 | \UPLOAD_ERR_NO_TMP_DIR => 'The temporary directory to write to is missing', |
|||||
129 | \UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk', |
||||||
130 | \UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload', |
||||||
131 | 13 | ]; |
|||||
132 | |||||||
133 | 13 | public static function streamNotAvailable(): \RuntimeException |
|||||
134 | 2 | { |
|||||
135 | return new \RuntimeException('Stream is not available, because the file was previously moved'); |
||||||
136 | } |
||||||
137 | 13 | ||||||
138 | 7 | public static function targetPathIsInvalid(): \InvalidArgumentException |
|||||
139 | { |
||||||
140 | return new \InvalidArgumentException('The provided path for moveTo operation is not valid'); |
||||||
141 | 6 | } |
|||||
142 | 1 | ||||||
143 | public static function fileAlreadyMoved(): \RuntimeException |
||||||
144 | 6 | { |
|||||
145 | return new \RuntimeException('File is not available, because it was previously moved'); |
||||||
146 | } |
||||||
147 | |||||||
148 | public static function fileNotSupported(): \InvalidArgumentException |
||||||
149 | { |
||||||
150 | return new \InvalidArgumentException('The uploaded file is not supported'); |
||||||
151 | } |
||||||
152 | |||||||
153 | public static function filenameCannotBeEmpty(): \InvalidArgumentException |
||||||
154 | { |
||||||
155 | return new \InvalidArgumentException('Filename cannot be empty'); |
||||||
156 | } |
||||||
157 | } |
||||||
158 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.