First Commit
This commit is contained in:
+270
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Console\Input\File;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidFileException;
|
||||
use Symfony\Component\Mime\MimeTypes;
|
||||
|
||||
/**
|
||||
* Represents a file provided through console input.
|
||||
*
|
||||
* Inspired by HttpFoundation's UploadedFile, this class wraps a file provided
|
||||
* through console input (either pasted via terminal image protocols or typed as a path).
|
||||
*
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
*/
|
||||
final class InputFile extends \SplFileInfo
|
||||
{
|
||||
/** @var string[] */
|
||||
private static array $tempFiles = [];
|
||||
private static bool $shutdownRegistered = false;
|
||||
|
||||
private ?string $mimeType = null;
|
||||
private bool $isTempFile;
|
||||
|
||||
public function __construct(
|
||||
string $path,
|
||||
bool $isTempFile = false,
|
||||
?string $mimeType = null,
|
||||
) {
|
||||
parent::__construct($path);
|
||||
|
||||
$this->isTempFile = $isTempFile;
|
||||
$this->mimeType = $mimeType;
|
||||
|
||||
if ($isTempFile) {
|
||||
if (!self::$shutdownRegistered) {
|
||||
register_shutdown_function(self::cleanupAll(...));
|
||||
self::$shutdownRegistered = true;
|
||||
}
|
||||
self::$tempFiles[$path] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidFileException when the temporary file cannot be created
|
||||
*/
|
||||
public static function fromData(string $data, ?string $format = null): self
|
||||
{
|
||||
$extension = $format ? '.'.$format : '';
|
||||
$tempPath = sys_get_temp_dir().'/symfony_input_'.bin2hex(random_bytes(8)).$extension;
|
||||
|
||||
$previousUmask = umask(0o077);
|
||||
|
||||
try {
|
||||
$handle = @fopen($tempPath, 'x');
|
||||
} finally {
|
||||
umask($previousUmask);
|
||||
}
|
||||
|
||||
if (false === $handle) {
|
||||
throw new InvalidFileException(\sprintf('Failed to create temporary file at "%s".', $tempPath));
|
||||
}
|
||||
|
||||
if (\strlen($data) !== @fwrite($handle, $data) || !@fclose($handle)) {
|
||||
@unlink($tempPath);
|
||||
|
||||
throw new InvalidFileException(\sprintf('Failed to create temporary file at "%s".', $tempPath));
|
||||
}
|
||||
|
||||
return new self($tempPath, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidFileException when the file does not exist
|
||||
*/
|
||||
public static function fromPath(string $path): self
|
||||
{
|
||||
$path = self::normalizePath($path);
|
||||
|
||||
if (!file_exists($path)) {
|
||||
throw new InvalidFileException(\sprintf('File "%s" does not exist.', $path));
|
||||
}
|
||||
|
||||
return new self($path, false);
|
||||
}
|
||||
|
||||
private static function normalizePath(string $path): string
|
||||
{
|
||||
$path = trim($path);
|
||||
|
||||
if (
|
||||
(str_starts_with($path, '"') && str_ends_with($path, '"'))
|
||||
|| (str_starts_with($path, "'") && str_ends_with($path, "'"))
|
||||
) {
|
||||
$path = substr($path, 1, -1);
|
||||
}
|
||||
|
||||
if (str_starts_with($path, 'file://')) {
|
||||
$path = urldecode(substr($path, 7));
|
||||
|
||||
if ('\\' === \DIRECTORY_SEPARATOR && preg_match('#^/[a-zA-Z]:/#', $path)) {
|
||||
$path = substr($path, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove backslash escapes (e.g., "\ " for escaped spaces) on non-Windows systems
|
||||
if ('\\' !== \DIRECTORY_SEPARATOR) {
|
||||
$path = preg_replace('/\\\\(.)/', '$1', $path) ?? $path;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function getMimeType(): ?string
|
||||
{
|
||||
if (null !== $this->mimeType) {
|
||||
return $this->mimeType;
|
||||
}
|
||||
|
||||
if (!$this->isValid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (class_exists(MimeTypes::class)) {
|
||||
return $this->mimeType = MimeTypes::getDefault()->guessMimeType($this->getPathname());
|
||||
}
|
||||
|
||||
$finfo = new \finfo(\FILEINFO_MIME_TYPE);
|
||||
|
||||
return $this->mimeType = $finfo->file($this->getPathname()) ?: null;
|
||||
}
|
||||
|
||||
public function guessExtension(): ?string
|
||||
{
|
||||
$mimeType = $this->getMimeType();
|
||||
|
||||
if (null === $mimeType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (class_exists(MimeTypes::class)) {
|
||||
$extensions = MimeTypes::getDefault()->getExtensions($mimeType);
|
||||
|
||||
return $extensions[0] ?? null;
|
||||
}
|
||||
|
||||
return match ($mimeType) {
|
||||
'image/png' => 'png',
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/gif' => 'gif',
|
||||
'image/webp' => 'webp',
|
||||
'image/svg+xml' => 'svg',
|
||||
'application/pdf' => 'pdf',
|
||||
'text/plain' => 'txt',
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidFileException when the file is invalid or the move/copy operation fails
|
||||
*/
|
||||
public function move(string $directory, ?string $name = null): self
|
||||
{
|
||||
if (!$this->isValid()) {
|
||||
throw new InvalidFileException('Cannot move an invalid file.');
|
||||
}
|
||||
|
||||
$name ??= $this->getFilename();
|
||||
$target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.$name;
|
||||
|
||||
if (!is_dir($directory)) {
|
||||
if (false === @mkdir($directory, 0o777, true) && !is_dir($directory)) {
|
||||
throw new InvalidFileException(\sprintf('Unable to create the "%s" directory.', $directory));
|
||||
}
|
||||
} elseif (!is_writable($directory)) {
|
||||
throw new InvalidFileException(\sprintf('Unable to write in the "%s" directory.', $directory));
|
||||
}
|
||||
|
||||
if ($this->isTempFile) {
|
||||
if (!@rename($this->getPathname(), $target)) {
|
||||
throw new InvalidFileException(\sprintf('Could not move the file "%s" to "%s".', $this->getPathname(), $target));
|
||||
}
|
||||
unset(self::$tempFiles[$this->getPathname()]);
|
||||
} else {
|
||||
if (!@copy($this->getPathname(), $target)) {
|
||||
throw new InvalidFileException(\sprintf('Could not copy the file "%s" to "%s".', $this->getPathname(), $target));
|
||||
}
|
||||
}
|
||||
|
||||
@chmod($target, 0o666 & ~umask());
|
||||
|
||||
return new self($target, false, $this->mimeType);
|
||||
}
|
||||
|
||||
public function cleanup(): void
|
||||
{
|
||||
if (!$this->isTempFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
$path = $this->getPathname();
|
||||
|
||||
if (file_exists($path)) {
|
||||
@unlink($path);
|
||||
}
|
||||
|
||||
unset(self::$tempFiles[$path]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function cleanupAll(): void
|
||||
{
|
||||
foreach (self::$tempFiles as $path) {
|
||||
if (file_exists($path)) {
|
||||
@unlink($path);
|
||||
}
|
||||
}
|
||||
|
||||
self::$tempFiles = [];
|
||||
}
|
||||
|
||||
public function isValid(): bool
|
||||
{
|
||||
return is_file($this->getPathname()) && is_readable($this->getPathname());
|
||||
}
|
||||
|
||||
public function isTempFile(): bool
|
||||
{
|
||||
return $this->isTempFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidFileException when the file is invalid or cannot be read
|
||||
*/
|
||||
public function getContents(): string
|
||||
{
|
||||
if (!$this->isValid()) {
|
||||
throw new InvalidFileException('Cannot read an invalid file.');
|
||||
}
|
||||
|
||||
$contents = @file_get_contents($this->getPathname());
|
||||
|
||||
if (false === $contents) {
|
||||
throw new InvalidFileException(\sprintf('Could not read file "%s".', $this->getPathname()));
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
public function getHumanReadableSize(): string
|
||||
{
|
||||
$size = $this->getSize();
|
||||
$units = ['B', 'KB', 'MB', 'GB'];
|
||||
$power = $size > 0 ? floor(log($size, 1024)) : 0;
|
||||
$power = min($power, \count($units) - 1);
|
||||
|
||||
return \sprintf('%.1f %s', $size / (1024 ** $power), $units[$power]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user