From 9964d06fd94b2192dd5990278d776bbbe3ec562f Mon Sep 17 00:00:00 2001 From: Jon Waldstein Date: Mon, 31 Jul 2023 15:18:13 -0400 Subject: [PATCH] feature: add allowedMimeType validation --- src/Framework/FieldsAPI/File.php | 52 ++++++++++++++++--- src/Framework/Http/Types/FileType.php | 48 ++++++++++++++--- src/Framework/ValidationRules/Rules/File.php | 54 ++++++++++++++++---- 3 files changed, 133 insertions(+), 21 deletions(-) diff --git a/src/Framework/FieldsAPI/File.php b/src/Framework/FieldsAPI/File.php index 2393442f9e..ffab9ff7c2 100644 --- a/src/Framework/FieldsAPI/File.php +++ b/src/Framework/FieldsAPI/File.php @@ -5,9 +5,6 @@ use Give\Framework\ValidationRules\Rules\AllowedTypes; use Give\Framework\ValidationRules\Rules\File as FileRule; -use function get_allowed_mime_types; -use function wp_max_upload_size; - /** * A file upload field. * @@ -32,11 +29,11 @@ public function maxSize(int $maxSize): File if ($this->hasRule(FileRule::id())) { /** @var FileRule $rule */ $rule = $this->getRule(FileRule::id()); - $rule->size($maxSize); + $rule->maxSize($maxSize); } // TODO: add support for file:maxSize - $this->rules((new FileRule())->size($maxSize)); + $this->rules((new FileRule())->maxSize($maxSize)); return $this; } @@ -53,12 +50,53 @@ public function getMaxSize(): int /** @var FileRule $rule */ $rule = $this->getRule(FileRule::id()); - return $rule->getSize(); + return $rule->getMaxSize(); + } + + /** + * Set the allowed file types. + * + * @unreleased + * + * @param string[] $allowedMimeTypes + */ + public function allowedMimeTypes(array $allowedMimeTypes): File + { + if ($this->hasRule(FileRule::id())) { + /** @var FileRule $rule */ + $rule = $this->getRule(FileRule::id()); + // TODO: add support for file:allowedMimeTypes + $rule->allowedMimeTypes($allowedMimeTypes); + } else { + $this->rules((new FileRule())->allowedMimeTypes($allowedMimeTypes)); + } + + + return $this; + } + + /** + * Access the allowed file mime types. + * + * @return string[] + */ + public function getAllowedMimeTypes(): array + { + if (!$this->hasRule(FileRule::id())) { + return get_allowed_mime_types(); + } + + /** @var FileRule $rule */ + $rule = $this->getRule(FileRule::id()); + + return $rule->getAllowedMimeTypes(); } /** * Set the allowed file types. * + * @deprecated use allowedMimeTypes() instead + * * @param string[] $allowedTypes */ public function allowedTypes(array $allowedTypes): File @@ -77,6 +115,8 @@ public function allowedTypes(array $allowedTypes): File /** * Access the allowed file types. * + * @deprecated use getAllowedMimeTypes() instead + * * @return string[] */ public function getAllowedTypes(): array diff --git a/src/Framework/Http/Types/FileType.php b/src/Framework/Http/Types/FileType.php index b26ddf483e..36e58396db 100644 --- a/src/Framework/Http/Types/FileType.php +++ b/src/Framework/Http/Types/FileType.php @@ -15,32 +15,32 @@ class FileType { * * @var string */ - public $name; + protected $name; /** * The mime type of the file, if the browser provided this information. An example would be "image/gif". This mime type is however not checked on the PHP side and therefore don't take its value for granted * * @var string */ - public $type; + protected $browserMimeType; /** * The temporary filename of the file in which the uploaded file was stored on the server. * * @var string */ - public $tmpName; + protected $tmpName; /** * The error code associated with this file upload. * * @see https://www.php.net/manual/en/features.file-upload.errors.php * @var int */ - public $error; + protected $error; /** * The size, in bytes, of the uploaded file * * @var int */ - public $size; + protected $size; /** * @unreleased @@ -50,11 +50,47 @@ public static function fromArray(array $fileArray): FileType $file = new self(); $file->name = (string)$fileArray['name']; - $file->type = (string)$fileArray['type']; + $file->browserMimeType = (string)$fileArray['type']; $file->tmpName = (string)$fileArray['tmp_name']; $file->error = (int)$fileArray['error']; $file->size = (int)$fileArray['size']; return $file; } + + /** + * @unreleased + * + * @see https://www.php.net/manual/en/function.is-uploaded-file.php + */ + public function isUploadedFile(): bool + { + return is_uploaded_file($this->tmpName); + } + + /** + * @unreleased + * + * @see https://www.php.net/manual/en/function.mime-content-type.php + */ + public function getMimeType(): string + { + return mime_content_type($this->tmpName); + } + + /** + * @unreleased + */ + public function getSize(): int + { + return $this->size; + } + + /** + * @unreleased + */ + public function getError(): int + { + return $this->error; + } } \ No newline at end of file diff --git a/src/Framework/ValidationRules/Rules/File.php b/src/Framework/ValidationRules/Rules/File.php index c706dcd934..c043edefd7 100644 --- a/src/Framework/ValidationRules/Rules/File.php +++ b/src/Framework/ValidationRules/Rules/File.php @@ -17,7 +17,12 @@ class File implements ValidationRule * * @var int */ - protected $size; + protected $maxSize; + + /** + * @var string[] + */ + protected $allowedMimeTypes; /** * @unreleased @@ -27,24 +32,45 @@ public static function id(): string return 'file'; } + + /** + * @unreleased + */ + public function maxSize(int $maxSize): ValidationRule + { + $this->maxSize = $maxSize; + + return $this; + } + /** * @unreleased */ - public function getSize(): int + public function getMaxSize(): int { - return $this->size; + return $this->maxSize; } /** * @unreleased */ - public function size(int $size): ValidationRule + public function allowedMimeTypes(array $allowedMimeTypes): ValidationRule { - $this->size = $size; + $this->allowedMimeTypes = $allowedMimeTypes; return $this; } + /** + * @unreleased + * + * @return string[] + */ + public function getAllowedMimeTypes(): array + { + return $this->allowedMimeTypes ?? []; + } + /** * @unreleased **/ @@ -53,12 +79,22 @@ public function __invoke($value, Closure $fail, string $key, array $values) try { $fileType = FileType::fromArray($value); - if ($fileType->size > $this->getSize()) { - $fail(sprintf(__('%s must be less than or equal to %d bytes', 'give'), '{field}', $this->getSize())); + if (!$fileType->isUploadedFile()) { + $fail(sprintf(__('%s must be a valid file.', 'give'), '{field}')); + } + + if (!in_array($fileType->getMimeType(), $this->getAllowedMimeTypes(), true)) { + $fail(sprintf(__('%s must be a valid file type.', 'give'), '{field}')); + } + + if ($fileType->getSize() > $this->getMaxSize()) { + $fail( + sprintf(__('%s must be less than or equal to %d bytes.', 'give'), '{field}', $this->getMaxSize()) + ); } - if ($fileType->error !== UPLOAD_ERR_OK) { - $fail(sprintf(__('%s must be a valid file', 'give'), '{field}')); + if ($fileType->getError() !== UPLOAD_ERR_OK) { + $fail(sprintf(__('%s must be a valid file.', 'give'), '{field}')); } } catch (\Throwable $e) { $fail($e->getMessage());