kodedphp /
http
| 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 | } 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
Loading history...
|
|||||||
| 44 | throw UploadedFileException::filenameCannotBeEmpty(); |
||||||
| 45 | } |
||||||
| 46 | // Never trust the provided mime type |
||||||
| 47 | $this->type = $this->getClientMediaType(); |
||||||
| 48 | } |
||||||
| 49 | |||||||
| 50 | public function getStream(): StreamInterface |
||||||
| 51 | { |
||||||
| 52 | if ($this->moved) { |
||||||
| 53 | throw UploadedFileException::streamNotAvailable(); |
||||||
| 54 | } |
||||||
| 55 | 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
Loading history...
|
|||||||
| 56 | } |
||||||
| 57 | |||||||
| 58 | public function moveTo($targetPath) |
||||||
| 59 | { |
||||||
| 60 | $this->assertUploadError(); |
||||||
| 61 | $this->assertTargetPath($targetPath); |
||||||
| 62 | // @codeCoverageIgnoreStart |
||||||
| 63 | 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
Loading history...
|
|||||||
| 66 | : \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
Loading history...
|
|||||||
| 67 | |||||||
| 68 | @\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
Loading history...
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.');
}
Loading history...
|
|||||||
| 69 | } catch (\Throwable $e) { |
||||||
| 70 | throw new \RuntimeException($e->getMessage()); |
||||||
| 71 | } |
||||||
| 72 | // @codeCoverageIgnoreEnd |
||||||
| 73 | } |
||||||
| 74 | |||||||
| 75 | public function getSize(): ?int |
||||||
| 76 | { |
||||||
| 77 | return $this->size; |
||||||
| 78 | } |
||||||
| 79 | |||||||
| 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 | try { |
||||||
| 93 | return (new \finfo(\FILEINFO_MIME_TYPE))->file($this->file); |
||||||
| 94 | } catch (\Throwable) { |
||||||
| 95 | return $this->type; |
||||||
| 96 | } |
||||||
| 97 | } |
||||||
| 98 | |||||||
| 99 | private function assertUploadError(): void |
||||||
| 100 | { |
||||||
| 101 | if ($this->error !== \UPLOAD_ERR_OK) { |
||||||
| 102 | throw new UploadedFileException($this->error); |
||||||
| 103 | } |
||||||
| 104 | } |
||||||
| 105 | |||||||
| 106 | private function assertTargetPath($targetPath): void |
||||||
| 107 | { |
||||||
| 108 | if ($this->moved) { |
||||||
| 109 | throw UploadedFileException::fileAlreadyMoved(); |
||||||
| 110 | } |
||||||
| 111 | if (false === \is_string($targetPath) || 0 === \mb_strlen($targetPath)) { |
||||||
| 112 | throw UploadedFileException::targetPathIsInvalid(); |
||||||
| 113 | } |
||||||
| 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.');
}
Loading history...
|
|||||||
| 116 | } |
||||||
| 117 | } |
||||||
| 118 | } |
||||||
| 119 | |||||||
| 120 | |||||||
| 121 | class UploadedFileException extends KodedException |
||||||
| 122 | { |
||||||
| 123 | protected array $messages = [ |
||||||
| 124 | \UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the "upload_max_filesize" directive in php.ini', |
||||||
| 125 | \UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the "MAX_FILE_SIZE" directive that was specified in the HTML form', |
||||||
| 126 | \UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded', |
||||||
| 127 | \UPLOAD_ERR_NO_FILE => 'No file was uploaded', |
||||||
| 128 | \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 | ]; |
||||||
| 132 | |||||||
| 133 | public static function streamNotAvailable(): \RuntimeException |
||||||
| 134 | { |
||||||
| 135 | return new \RuntimeException('Stream is not available, because the file was previously moved'); |
||||||
| 136 | } |
||||||
| 137 | |||||||
| 138 | public static function targetPathIsInvalid(): \InvalidArgumentException |
||||||
| 139 | { |
||||||
| 140 | return new \InvalidArgumentException('The provided path for moveTo operation is not valid'); |
||||||
| 141 | } |
||||||
| 142 | |||||||
| 143 | public static function fileAlreadyMoved(): \RuntimeException |
||||||
| 144 | { |
||||||
| 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.