diff --git a/src/Illuminate/Translation/lang/en/validation.php b/src/Illuminate/Translation/lang/en/validation.php index bd9cc110226b..053eb3ae2afa 100644 --- a/src/Illuminate/Translation/lang/en/validation.php +++ b/src/Illuminate/Translation/lang/en/validation.php @@ -52,6 +52,7 @@ 'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.', 'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.', 'email' => 'The :attribute field must be a valid email address.', + 'encoding' => 'The :attribute field must encoded in :encoding.', 'ends_with' => 'The :attribute field must end with one of the following: :values.', 'enum' => 'The selected :attribute is invalid.', 'exists' => 'The selected :attribute is invalid.', diff --git a/src/Illuminate/Validation/Concerns/ReplacesAttributes.php b/src/Illuminate/Validation/Concerns/ReplacesAttributes.php index ef9bfaf6bca3..62ebbfbb4b95 100644 --- a/src/Illuminate/Validation/Concerns/ReplacesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ReplacesAttributes.php @@ -129,6 +129,20 @@ protected function replaceDigitsBetween($message, $attribute, $rule, $parameters return $this->replaceBetween($message, $attribute, $rule, $parameters); } + /** + * Replace all place-holders for the encoding rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceEncoding($message, $attribute, $rule, $parameters) + { + return str_replace(':encoding', $parameters[0], $message); + } + /** * Replace all place-holders for the extensions rule. * diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index 1e55e3f68a86..854fcc831443 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -959,6 +959,25 @@ public function validateEmail($attribute, $value, $parameters) return $emailValidator->isValid($value, new MultipleValidationWithAnd($validations)); } + /** + * Validate the encoding of an attribute. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + public function validateEncoding($attribute, $value, $parameters) + { + $this->requireParameterCount(1, $parameters, 'encoding'); + + if (! in_array(mb_strtolower($parameters[0]), array_map(mb_strtolower(...), mb_list_encodings()))) { + throw new InvalidArgumentException("Validation rule encoding parameter [{$parameters[0]}] is not a valid encoding."); + } + + return mb_check_encoding($value instanceof File ? $value->getContent() : $value, $parameters[0]); + } + /** * Validate the existence of an attribute value in a database table. * diff --git a/src/Illuminate/Validation/Rules/File.php b/src/Illuminate/Validation/Rules/File.php index b7589853e9d2..83aa36e7b9b0 100644 --- a/src/Illuminate/Validation/Rules/File.php +++ b/src/Illuminate/Validation/Rules/File.php @@ -45,6 +45,13 @@ class File implements Rule, DataAwareRule, ValidatorAwareRule */ protected $maximumFileSize = null; + /** + * The required file encoding. + * + * @var string|null + */ + protected $encoding = null; + /** * An array of custom rules that will be merged into the validation rules. * @@ -205,6 +212,19 @@ public function max($size) return $this; } + /** + * Indicate that the uploaded file should be in the given encoding. + * + * @param string $encoding + * @return $this + */ + public function encoding($encoding) + { + $this->encoding = $encoding; + + return $this; + } + /** * Convert a potentially human-friendly file size to kilobytes. * @@ -291,6 +311,10 @@ protected function buildValidationRules() default => "size:{$this->minimumFileSize}", }; + if ($this->encoding) { + $rules[] = 'encoding:'.$this->encoding; + } + return array_merge(array_filter($rules), $this->customRules); } diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 5d18ceba448f..064b04393dd7 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -188,6 +188,7 @@ class Validator implements ValidatorContract protected $fileRules = [ 'Between', 'Dimensions', + 'Encoding', 'Extensions', 'File', 'Image', diff --git a/tests/Validation/ValidationFileRuleTest.php b/tests/Validation/ValidationFileRuleTest.php index 0d95bb2a8e66..5f036f2a0cb6 100644 --- a/tests/Validation/ValidationFileRuleTest.php +++ b/tests/Validation/ValidationFileRuleTest.php @@ -357,6 +357,49 @@ public function testMaxWithHumanReadableSizeAndMultipleValue() ); } + public function testEncoding() + { + // ASCII file containing UTF-8. + $this->fails( + File::default()->encoding('ascii'), + UploadedFile::fake()->createWithContent('foo.txt', '✌️'), + ['validation.encoding'], + ); + + // UTF-8 file containing invalid UTF-8 byte sequence. + $this->fails( + File::default()->encoding('utf-8'), + UploadedFile::fake()->createWithContent('foo.txt', "\xf0\x28\x8c\x28"), + ['validation.encoding'], + ); + + $this->passes( + File::default()->encoding('utf-8'), + UploadedFile::fake()->createWithContent('foo.txt', '✌️'), + ); + + $this->passes( + File::default()->encoding('utf-8'), + [ + UploadedFile::fake()->createWithContent('foo-1.txt', '✌️'), + UploadedFile::fake()->createWithContent('foo-2.txt', '👍'), + ] + ); + } + + public function testEncodingWithInvalidParameter() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Validation rule encoding parameter "FOOBAR" is not a valid encoding.'); + + // Invalid encoding. + $this->fails( + File::default()->encoding('FOOBAR'), + UploadedFile::fake()->createWithContent('foo.txt', ''), + ['validation.encoding'], + ); + } + public function testMacro() { File::macro('toDocument', function () {