Browse Source

Add support for PSR7 file objects in Validation methods.

Add support for PSR7 UploadedFile objects in the various file upload
validators. This will make integrating UploadedFile instances much
simpler into existing ORM workflows that deal with uploaded files.
Mark Story 9 years ago
parent
commit
e646773b85
2 changed files with 137 additions and 23 deletions
  1. 56 21
      src/Validation/Validation.php
  2. 81 2
      tests/TestCase/Validation/ValidationTest.php

+ 56 - 21
src/Validation/Validation.php

@@ -945,7 +945,11 @@ class Validation
     /**
      * Checks the mime type of a file.
      *
-     * @param string|array $check Value to check.
+     * Will check the mimetype of files/UploadedFileInterface instances
+     * by checking the using finfo on the file, not relying on the content-type
+     * sent by the client.
+     *
+     * @param string|array|\Psr\Http\Message\UploadedFileInterface $check Value to check.
      * @param array|string $mimeTypes Array of mime types or regex pattern to check.
      * @return bool Success
      * @throws \RuntimeException when mime type can not be determined.
@@ -953,20 +957,29 @@ class Validation
      */
     public static function mimeType($check, $mimeTypes = [])
     {
-        if (is_array($check) && isset($check['tmp_name'])) {
-            $check = $check['tmp_name'];
+        if ($check instanceof UploadedFileInterface) {
+            try {
+                // Uploaded files throw exceptions on upload errors.
+                $file = $check->getStream()->getMetadata('uri');
+            } catch (RuntimeException $e) {
+                return false;
+            }
+        } elseif (is_array($check) && isset($check['tmp_name'])) {
+            $file = $check['tmp_name'];
+        } else {
+            $file = $check;
         }
 
         if (!function_exists('finfo_open')) {
             throw new LogicException('ext/fileinfo is required for validating file mime types');
         }
 
-        if (!is_file($check)) {
+        if (!is_file($file)) {
             throw new RuntimeException('Cannot validate mimetype for a missing file');
         }
 
         $finfo = finfo_open(FILEINFO_MIME);
-        $finfo = finfo_file($finfo, $check);
+        $finfo = finfo_file($finfo, $file);
 
         if (!$finfo) {
             throw new RuntimeException('Can not determine the mimetype.');
@@ -988,21 +1001,29 @@ class Validation
     /**
      * Checks the filesize
      *
-     * @param string|array $check Value to check.
+     * Will check the filesize of files/UploadedFileInterface instances
+     * by checking the filesize() on disk and not relying on the length
+     * reported by the client.
+     *
+     * @param string|array|\Psr\Http\Message\UploadedFileInterface $check Value to check.
      * @param string|null $operator See `Validation::comparison()`.
      * @param int|string|null $size Size in bytes or human readable string like '5MB'.
      * @return bool Success
      */
     public static function fileSize($check, $operator = null, $size = null)
     {
-        if (is_array($check) && isset($check['tmp_name'])) {
-            $check = $check['tmp_name'];
+        if ($check instanceof UploadedFileInterface) {
+            $file = $check->getStream()->getMetadata('uri');
+        } elseif (is_array($check) && isset($check['tmp_name'])) {
+            $file = $check['tmp_name'];
+        } else {
+            $file = $check;
         }
 
         if (is_string($size)) {
             $size = Text::parseFileSize($size);
         }
-        $filesize = filesize($check);
+        $filesize = filesize($file);
 
         return static::comparison($filesize, $operator, $size);
     }
@@ -1010,21 +1031,25 @@ class Validation
     /**
      * Checking for upload errors
      *
-     * @param string|array $check Value to check.
+     * @param string|array|\Psr\Http\Message\UploadedFileInterface $check Value to check.
      * @param bool $allowNoFile Set to true to allow UPLOAD_ERR_NO_FILE as a pass.
      * @return bool
      * @see http://www.php.net/manual/en/features.file-upload.errors.php
      */
     public static function uploadError($check, $allowNoFile = false)
     {
-        if (is_array($check) && isset($check['error'])) {
-            $check = $check['error'];
+        if ($check instanceof UploadedFileInterface) {
+            $code = $check->getError();
+        } elseif (is_array($check) && isset($check['error'])) {
+            $code = $check['error'];
+        } else {
+            $code = $check;
         }
         if ($allowNoFile) {
-            return in_array((int)$check, [UPLOAD_ERR_OK, UPLOAD_ERR_NO_FILE], true);
+            return in_array((int)$code, [UPLOAD_ERR_OK, UPLOAD_ERR_NO_FILE], true);
         }
 
-        return (int)$check === UPLOAD_ERR_OK;
+        return (int)$code === UPLOAD_ERR_OK;
     }
 
     /**
@@ -1055,18 +1080,28 @@ class Validation
             'types' => null,
             'optional' => false,
         ];
-        if (!is_array($file)) {
+        if (!is_array($file) && !($file instanceof UploadedFileInterface)) {
             return false;
         }
-        $keys = ['error', 'name', 'size', 'tmp_name', 'type'];
-        ksort($file);
-        if (array_keys($file) != $keys) {
-            return false;
+        $error = $isUploaded = false;
+        if ($file instanceof UploadedFileInterface) {
+            $error = $file->getError();
+            $isUploaded = true;
+        }
+        if (is_array($file)) {
+            $keys = ['error', 'name', 'size', 'tmp_name', 'type'];
+            ksort($file);
+            if (array_keys($file) != $keys) {
+                return false;
+            }
+            $error = (int)$file['error'];
+            $isUploaded = is_uploaded_file($file['tmp_name']);
         }
+
         if (!static::uploadError($file, $options['optional'])) {
             return false;
         }
-        if ($options['optional'] && (int)$file['error'] === UPLOAD_ERR_NO_FILE) {
+        if ($options['optional'] && $error === UPLOAD_ERR_NO_FILE) {
             return true;
         }
         if (isset($options['minSize']) && !static::fileSize($file, '>=', $options['minSize'])) {
@@ -1079,7 +1114,7 @@ class Validation
             return false;
         }
 
-        return is_uploaded_file($file['tmp_name']);
+        return $isUploaded;
     }
 
     /**

+ 81 - 2
tests/TestCase/Validation/ValidationTest.php

@@ -21,6 +21,7 @@ use Cake\I18n\I18n;
 use Cake\TestSuite\TestCase;
 use Cake\Validation\Validation;
 use Cake\Validation\Validator;
+use Zend\Diactoros\UploadedFile;
 use Locale;
 
 require_once __DIR__ . '/stubs.php';
@@ -2362,7 +2363,7 @@ class ValidationTest extends TestCase
      */
     public function testMimeType()
     {
-        $image = CORE_TESTS . 'test_app/webroot/img/cake.power.gif';
+        $image = TEST_APP . 'webroot/img/cake.power.gif';
         $File = new File($image, false);
 
         $this->skipIf(!$File->mime(), 'Cannot determine mimeType');
@@ -2377,6 +2378,23 @@ class ValidationTest extends TestCase
     }
 
     /**
+     * Test mimetype with a PSR7 object
+     *
+     * @return void
+     */
+    public function testMimeTypePsr7()
+    {
+        $image = TEST_APP . 'webroot/img/cake.power.gif';
+        $file = new UploadedFile($image, 1000, UPLOAD_ERR_OK, 'cake.power.gif', 'image/lies');
+        $this->assertTrue(Validation::mimeType($file, ['image/gif']));
+        $this->assertFalse(Validation::mimeType($file, ['image/png']));
+
+        $image = CORE_TESTS . 'test_app/webroot/img/cake.power.gif';
+        $file = new UploadedFile($image, 1000, UPLOAD_ERR_INI_SIZE, 'cake.power.gif', 'image/lies');
+        $this->assertFalse(Validation::mimeType($file, ['image/gif']), 'Fails on upload error');
+    }
+
+    /**
      * testMimeTypeFalse method
      *
      * @expectedException \RuntimeException
@@ -2384,7 +2402,7 @@ class ValidationTest extends TestCase
      */
     public function testMimeTypeFalse()
     {
-        $image = CORE_PATH . 'Cake/Test/TestApp/webroot/img/cake.power.gif';
+        $image = CORE_TESTS . 'invalid-file.png';
         $File = new File($image, false);
         $this->skipIf($File->mime(), 'mimeType can be determined, no Exception will be thrown');
         Validation::mimeType($image, ['image/gif']);
@@ -2413,6 +2431,22 @@ class ValidationTest extends TestCase
     }
 
     /**
+     * testUploadError method with an UploadedFile
+     *
+     * @return void
+     */
+    public function testUploadErrorPsr7()
+    {
+        $image = TEST_APP . 'webroot/img/cake.power.gif';
+        $file = new UploadedFile($image, 1000, UPLOAD_ERR_OK, 'cake.power.gif', 'image/gif');
+        $this->assertTrue(Validation::uploadError($file));
+
+        $file = new UploadedFile($image, 1000, UPLOAD_ERR_NO_FILE, 'cake.power.gif', 'image/gif');
+        $this->assertFalse(Validation::uploadError($file));
+        $this->assertTrue(Validation::uploadError($file, true));
+    }
+
+    /**
      * testFileSize method
      *
      * @return void
@@ -2432,6 +2466,22 @@ class ValidationTest extends TestCase
     }
 
     /**
+     * Test fileSize() with a PSR7 object.
+     *
+     * @return void
+     */
+    public function testFileSizePsr7()
+    {
+        $image = TEST_APP . 'webroot/img/cake.power.gif';
+        $file = new UploadedFile($image, 1000, UPLOAD_ERR_OK, 'cake.power.gif', 'image/gif');
+
+        $this->assertTrue(Validation::fileSize($file, '==', 201));
+        $this->assertTrue(Validation::fileSize($file, '<', 1024));
+        $this->assertFalse(Validation::fileSize($file, '>', 202));
+        $this->assertFalse(Validation::fileSize($file, '>', 1000));
+    }
+
+    /**
      * Test uploaded file validation.
      *
      * @return void
@@ -2553,6 +2603,35 @@ class ValidationTest extends TestCase
     }
 
     /**
+     * Test uploadedFile with a PSR7 object.
+     *
+     * @return void
+     */
+    public function testUploadedFilePsr7()
+    {
+        $image = TEST_APP . 'webroot/img/cake.power.gif';
+        $file = new UploadedFile($image, 1000, UPLOAD_ERR_OK, 'cake.power.gif', 'image/gif');
+
+        $options = ['minSize' => 500];
+        $this->assertFalse(Validation::uploadedFile($file, $options));
+
+        $options = ['minSize' => 190];
+        $this->assertTrue(Validation::uploadedFile($file, $options));
+
+        $options = ['maxSize' => 10];
+        $this->assertFalse(Validation::uploadedFile($file, $options));
+
+        $options = ['maxSize' => 1000];
+        $this->assertTrue(Validation::uploadedFile($file, $options));
+
+        $options = ['types' => ['image/gif', 'image/png']];
+        $this->assertTrue(Validation::uploadedFile($file, $options));
+
+        $options = ['types' => ['text/plain']];
+        $this->assertFalse(Validation::uploadedFile($file, $options));
+    }
+
+    /**
      * Test the compareWith method.
      *
      * @return void