/wcfsetup/install/files/lib/system/api/laminas/laminas-diactoros/src/UploadedFile.php

https://github.com/WoltLab/WCF · PHP · 262 lines · 155 code · 35 blank · 72 comment · 24 complexity · 6a3a635dd2b66f0eec8cd2d0a703e6d0 MD5 · raw file

  1. <?php
  2. declare(strict_types=1);
  3. namespace Laminas\Diactoros;
  4. use Psr\Http\Message\StreamInterface;
  5. use Psr\Http\Message\UploadedFileInterface;
  6. use function dirname;
  7. use function fclose;
  8. use function fopen;
  9. use function fwrite;
  10. use function is_dir;
  11. use function is_int;
  12. use function is_resource;
  13. use function is_string;
  14. use function is_writable;
  15. use function move_uploaded_file;
  16. use function sprintf;
  17. use function strpos;
  18. use const PHP_SAPI;
  19. use const UPLOAD_ERR_CANT_WRITE;
  20. use const UPLOAD_ERR_EXTENSION;
  21. use const UPLOAD_ERR_FORM_SIZE;
  22. use const UPLOAD_ERR_INI_SIZE;
  23. use const UPLOAD_ERR_NO_FILE;
  24. use const UPLOAD_ERR_NO_TMP_DIR;
  25. use const UPLOAD_ERR_OK;
  26. use const UPLOAD_ERR_PARTIAL;
  27. class UploadedFile implements UploadedFileInterface
  28. {
  29. public const ERROR_MESSAGES = [
  30. UPLOAD_ERR_OK => 'There is no error, the file uploaded with success',
  31. UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
  32. UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was '
  33. . 'specified in the HTML form',
  34. UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded',
  35. UPLOAD_ERR_NO_FILE => 'No file was uploaded',
  36. UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder',
  37. UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
  38. UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.',
  39. ];
  40. /**
  41. * @var string|null
  42. */
  43. private $clientFilename;
  44. /**
  45. * @var string|null
  46. */
  47. private $clientMediaType;
  48. /**
  49. * @var int
  50. */
  51. private $error;
  52. /**
  53. * @var null|string
  54. */
  55. private $file;
  56. /**
  57. * @var bool
  58. */
  59. private $moved = false;
  60. /**
  61. * @var int
  62. */
  63. private $size;
  64. /**
  65. * @var null|StreamInterface
  66. */
  67. private $stream;
  68. /**
  69. * @param string|resource|StreamInterface $streamOrFile
  70. * @param int $size
  71. * @param int $errorStatus
  72. * @param string|null $clientFilename
  73. * @param string|null $clientMediaType
  74. * @throws Exception\InvalidArgumentException
  75. */
  76. public function __construct(
  77. $streamOrFile,
  78. int $size,
  79. int $errorStatus,
  80. string $clientFilename = null,
  81. string $clientMediaType = null
  82. ) {
  83. if ($errorStatus === UPLOAD_ERR_OK) {
  84. if (is_string($streamOrFile)) {
  85. $this->file = $streamOrFile;
  86. }
  87. if (is_resource($streamOrFile)) {
  88. $this->stream = new Stream($streamOrFile);
  89. }
  90. if (! $this->file && ! $this->stream) {
  91. if (! $streamOrFile instanceof StreamInterface) {
  92. throw new Exception\InvalidArgumentException('Invalid stream or file provided for UploadedFile');
  93. }
  94. $this->stream = $streamOrFile;
  95. }
  96. }
  97. $this->size = $size;
  98. if (0 > $errorStatus || 8 < $errorStatus) {
  99. throw new Exception\InvalidArgumentException(
  100. 'Invalid error status for UploadedFile; must be an UPLOAD_ERR_* constant'
  101. );
  102. }
  103. $this->error = $errorStatus;
  104. $this->clientFilename = $clientFilename;
  105. $this->clientMediaType = $clientMediaType;
  106. }
  107. /**
  108. * {@inheritdoc}
  109. * @throws Exception\UploadedFileAlreadyMovedException if the upload was
  110. * not successful.
  111. */
  112. public function getStream() : StreamInterface
  113. {
  114. if ($this->error !== UPLOAD_ERR_OK) {
  115. throw Exception\UploadedFileErrorException::dueToStreamUploadError(
  116. self::ERROR_MESSAGES[$this->error]
  117. );
  118. }
  119. if ($this->moved) {
  120. throw new Exception\UploadedFileAlreadyMovedException();
  121. }
  122. if ($this->stream instanceof StreamInterface) {
  123. return $this->stream;
  124. }
  125. $this->stream = new Stream($this->file);
  126. return $this->stream;
  127. }
  128. /**
  129. * {@inheritdoc}
  130. *
  131. * @see http://php.net/is_uploaded_file
  132. * @see http://php.net/move_uploaded_file
  133. * @param string $targetPath Path to which to move the uploaded file.
  134. * @throws Exception\UploadedFileErrorException if the upload was not successful.
  135. * @throws Exception\InvalidArgumentException if the $path specified is invalid.
  136. * @throws Exception\UploadedFileErrorException on any error during the
  137. * move operation, or on the second or subsequent call to the method.
  138. */
  139. public function moveTo($targetPath) : void
  140. {
  141. if ($this->moved) {
  142. throw new Exception\UploadedFileAlreadyMovedException('Cannot move file; already moved!');
  143. }
  144. if ($this->error !== UPLOAD_ERR_OK) {
  145. throw Exception\UploadedFileErrorException::dueToStreamUploadError(
  146. self::ERROR_MESSAGES[$this->error]
  147. );
  148. }
  149. if (! is_string($targetPath) || empty($targetPath)) {
  150. throw new Exception\InvalidArgumentException(
  151. 'Invalid path provided for move operation; must be a non-empty string'
  152. );
  153. }
  154. $targetDirectory = dirname($targetPath);
  155. if (! is_dir($targetDirectory) || ! is_writable($targetDirectory)) {
  156. throw Exception\UploadedFileErrorException::dueToUnwritableTarget($targetDirectory);
  157. }
  158. $sapi = PHP_SAPI;
  159. switch (true) {
  160. case (empty($sapi) || 0 === strpos($sapi, 'cli') || 0 === strpos($sapi, 'phpdbg') || ! $this->file):
  161. // Non-SAPI environment, or no filename present
  162. $this->writeFile($targetPath);
  163. break;
  164. default:
  165. // SAPI environment, with file present
  166. if (false === move_uploaded_file($this->file, $targetPath)) {
  167. throw Exception\UploadedFileErrorException::forUnmovableFile();
  168. }
  169. break;
  170. }
  171. $this->moved = true;
  172. }
  173. /**
  174. * {@inheritdoc}
  175. *
  176. * @return int|null The file size in bytes or null if unknown.
  177. */
  178. public function getSize() : ?int
  179. {
  180. return $this->size;
  181. }
  182. /**
  183. * {@inheritdoc}
  184. *
  185. * @see http://php.net/manual/en/features.file-upload.errors.php
  186. * @return int One of PHP's UPLOAD_ERR_XXX constants.
  187. */
  188. public function getError() : int
  189. {
  190. return $this->error;
  191. }
  192. /**
  193. * {@inheritdoc}
  194. *
  195. * @return string|null The filename sent by the client or null if none
  196. * was provided.
  197. */
  198. public function getClientFilename() : ?string
  199. {
  200. return $this->clientFilename;
  201. }
  202. /**
  203. * {@inheritdoc}
  204. */
  205. public function getClientMediaType() : ?string
  206. {
  207. return $this->clientMediaType;
  208. }
  209. /**
  210. * Write internal stream to given path
  211. *
  212. * @param string $path
  213. */
  214. private function writeFile(string $path) : void
  215. {
  216. $handle = fopen($path, 'wb+');
  217. if (false === $handle) {
  218. throw Exception\UploadedFileErrorException::dueToUnwritablePath();
  219. }
  220. $stream = $this->getStream();
  221. $stream->rewind();
  222. while (! $stream->eof()) {
  223. fwrite($handle, $stream->read(4096));
  224. }
  225. fclose($handle);
  226. }
  227. }