From 4ab7fbabcc8caba5d397d1a590acb13e334a2960 Mon Sep 17 00:00:00 2001 From: Josh Pollock Date: Thu, 25 Oct 2018 15:52:45 -0400 Subject: [PATCH] check file types before saving #2766 --- cf2/Exception.php | 56 ++++++ cf2/Fields/Handlers/FileFieldHandler.php | 10 + cf2/Fields/Handlers/FileHandlerContract.php | 12 ++ cf2/Fields/Handlers/FileUpload.php | 47 ++++- tests/Integration/FileUploadTest.php | 183 +++++++++++++++++++ tests/Integration/RestApi/CreateFileTest.php | 41 +++++ tests/Unit/ExceptionTest.php | 66 +++++++ tests/Util/Mocks/MockUploader.php | 9 + tests/includes/forms/cf2-file-include.php | 23 ++- 9 files changed, 436 insertions(+), 11 deletions(-) create mode 100644 cf2/Exception.php create mode 100644 cf2/Fields/Handlers/FileFieldHandler.php create mode 100644 cf2/Fields/Handlers/FileHandlerContract.php create mode 100644 tests/Unit/ExceptionTest.php diff --git a/cf2/Exception.php b/cf2/Exception.php new file mode 100644 index 000000000..a2c6c24a1 --- /dev/null +++ b/cf2/Exception.php @@ -0,0 +1,56 @@ +getCode(), $this->getMessage(), $data); + } + + /** + * @param array $data + * @param array $headers + * @return \WP_REST_Response + */ + public function toResponse(array $data = [], array $headers = []) + { + $data = array_merge($data, ['message' => $this->getMessage()]); + return new \WP_REST_Response($data, absint($this->getCode() ? $this->getCode() : 500), $headers); + } + + /** + * Convert any Exception to this type of Exception + * + * @param \Exception $exception + * @return Exception + */ + public static function formOtherException(\Exception $exception) + { + return new static( + $exception->getMessage(), + $exception->getCode(), + $exception + ); + } + + /** + * Convert a WP_Error object to an Exception + * @param \WP_Error $error + * @return Exception + */ + public static function fromWpError(\WP_Error $error) + { + return new static( + $error->get_error_message(), + $error->get_error_code() + ); + } +} \ No newline at end of file diff --git a/cf2/Fields/Handlers/FileFieldHandler.php b/cf2/Fields/Handlers/FileFieldHandler.php new file mode 100644 index 000000000..e95c15ffb --- /dev/null +++ b/cf2/Fields/Handlers/FileFieldHandler.php @@ -0,0 +1,10 @@ +field); - - $uploadArgs = array( - 'private' => true, - 'field_id' => $this->field['ID'], - 'form_id' => $this->form['ID'] - ); - - - $expected = $hashes[$i]; $actual = md5_file( $file['tmp_name'] ); @@ -66,7 +58,9 @@ public function processFiles(array $files,array $hashes ){ $isPrivate ); - + if( ! $this->isAllowedType( $file ) ){ + throw new Exception( __('This file type is not allowed. Please try another.', 'caldera-forms'), 415 ); + } $upload = wp_handle_upload($file, array( 'test_form' => false, 'action' => 'foo' ) ); $this->uploader->removeFilter(); if( !empty( $field['config']['media_lib'] ) ){ @@ -83,4 +77,37 @@ public function processFiles(array $files,array $hashes ){ return $uploads; } + + /** + * Check if file type if allowed for this field + * + * @since 1.8.0 + * + * @param $file + * @return bool + * @throws Exception + */ + public function isAllowedType($file){ + if( empty( $this->field['config']['allowed'] )){ + return true; + } + $filetype = wp_check_filetype( basename( $file['tmp_name'] ), null ); + return in_array( strtolower( $filetype['ext'] ), $this->getAllowedTypes() ); + } + + /** + * Get allowed file types for file field + * + * @since 1.8.0 + * + * @return array + */ + public function getAllowedTypes() + { + $types = ! empty( $this->field['config']['allowed'] ) ? $this->field['config']['allowed'] : []; + if( ! is_array( $types ) ){ + $types = explode(',', $types ); + } + return $types; + } } \ No newline at end of file diff --git a/tests/Integration/FileUploadTest.php b/tests/Integration/FileUploadTest.php index 3bbff6865..cd060fbe5 100644 --- a/tests/Integration/FileUploadTest.php +++ b/tests/Integration/FileUploadTest.php @@ -2,6 +2,7 @@ namespace calderawp\calderaforms\Tests\IntegrationFields\Handlers; +use calderawp\calderaforms\cf2\Exception; use calderawp\calderaforms\cf2\Fields\Handlers\Cf1FileUploader; use calderawp\calderaforms\cf2\Fields\Handlers\FileUpload; use calderawp\calderaforms\cf2\Transients\Cf1TransientsApi; @@ -110,4 +111,186 @@ public function testFilterDirectoryForUpload(){ $this->assertNotFalse( strpos($uploads[0], 'form-uploads'), $uploads[0]); } + + /** + * @since 1.8.0 + * + * @covers \calderawp\calderaforms\cf2\Fields\Handlers\FileUpload::isAllowedType() + * + * @group now + * @group cf2 + * @group file + * @group field + * @group cf2_file + * + * @throws \calderawp\calderaforms\cf2\Exception + */ + public function testAllTypesAllowedWhenNotSpecified() + { + $formId = 'cf2_file'; + $fieldId = 'cf2_file_1'; + $form = \Caldera_Forms_Forms::get_form( $formId ); + $field = \Caldera_Forms_Field_Util::get_field($fieldId,$form); + + $files = [ + [ + 'file' => file_get_contents($this->test_file), + 'name' => 'screenshot.jpeg', + 'size' => filesize($this->test_file), + 'tmp_name' => $this->test_file, + ] + ]; + + $handler = new FileUpload( + $field, + $field, + new Cf1FileUploader() + ); + + $this->assertTrue( $handler->isAllowedType( $files[0] ) ); + + } + + /** + * @since 1.8.0 + * + * @covers \calderawp\calderaforms\cf2\Fields\Handlers\FileUpload::isAllowedType() + * + * @group cf2 + * @group file + * @group field + * @group cf2_file + * + * @throws \calderawp\calderaforms\cf2\Exception + */ + public function testTypesAllowedWhenSpecified() + { + $formId = 'cf2_file'; + $fieldId = 'cf2_file_2'; + $form = \Caldera_Forms_Forms::get_form( $formId ); + $field = \Caldera_Forms_Field_Util::get_field($fieldId,$form); + $this->assertFalse( \Caldera_Forms_Files::is_private($field) ); + + $files = [ + [ + 'file' => file_get_contents($this->test_file), + 'name' => 'screenshot.png', + 'size' => filesize($this->test_file), + 'tmp_name' => $this->test_file, + ] + ]; + + $handler = new FileUpload( + $field, + $field, + new Cf1FileUploader() + ); + + $this->assertTrue( $handler->isAllowedType( $files[0] ) ); + + } + + + /** + * @since 1.8.0 + * + * @covers \calderawp\calderaforms\cf2\Fields\Handlers\FileUpload::getAllowedTypes() + * + * @group cf2 + * @group file + * @group field + * @group cf2_file + * + */ + public function testGetAllowedTypes(){ + $formId = 'cf2_file'; + $fieldId = 'cf2_file_2'; + $form = \Caldera_Forms_Forms::get_form( $formId ); + $field = \Caldera_Forms_Field_Util::get_field($fieldId,$form); + $handler = new FileUpload( + $field, + $field, + new Cf1FileUploader() + ); + $this->assertTrue(is_array( $handler->getAllowedTypes() ) ); + $this->assertTrue( in_array( 'png', $handler->getAllowedTypes() ) ); + $this->assertTrue( in_array( 'jpg', $handler->getAllowedTypes() ) ); + } + + /** + * @since 1.8.0 + * + * @covers \calderawp\calderaforms\cf2\Fields\Handlers\FileUpload::isAllowedType() + * + * @group cf2 + * @group file + * @group field + * @group cf2_file + * + * @throws \calderawp\calderaforms\cf2\Exception + */ + public function testTypesNotAllowedWhenNotSpecified() + { + $formId = 'cf2_file'; + $fieldId = 'cf2_file_2'; + $form = \Caldera_Forms_Forms::get_form( $formId ); + $field = \Caldera_Forms_Field_Util::get_field($fieldId,$form); + $this->assertFalse( \Caldera_Forms_Files::is_private($field) ); + + $files = [ + [ + 'file' => file_get_contents($this->test_file), + 'name' => 'screenshot.gif', + 'size' => filesize($this->test_file), + 'tmp_name' => '/tmp/screenshot.gif', + ] + ]; + + $handler = new FileUpload( + $field, + $field, + new Cf1FileUploader() + ); + $this->assertFalse( $handler->isAllowedType( $files[0] ) ); + + } + + /** + * @since 1.8.0 + * + * @covers \calderawp\calderaforms\cf2\Fields\Handlers\FileUpload::isAllowedType() + * @covers \calderawp\calderaforms\cf2\Fields\Handlers\FileUpload::processFiles() + * + * @group cf2 + * @group file + * @group field + * @group cf2_file + * + */ + public function testProcessInvalidTypeThrowsException() + { + $this->expectException(Exception::class); + $formId = 'cf2_file'; + $fieldId = 'cf2_file_3'; + $form = \Caldera_Forms_Forms::get_form( $formId ); + $field = \Caldera_Forms_Field_Util::get_field($fieldId,$form); + $this->assertFalse( \Caldera_Forms_Files::is_private($field) ); + + $files = [ + [ + 'file' => file_get_contents($this->test_file), + 'name' => 'screenshot.jpeg', + 'size' => filesize($this->test_file), + 'tmp_name' => $this->test_file, + ] + ]; + + $handler = new FileUpload( + $field, + $field, + new Cf1FileUploader() + ); + $handler->processFiles($files, [md5_file($this->test_file)]); + } + } diff --git a/tests/Integration/RestApi/CreateFileTest.php b/tests/Integration/RestApi/CreateFileTest.php index 35f8eb445..534eaef47 100644 --- a/tests/Integration/RestApi/CreateFileTest.php +++ b/tests/Integration/RestApi/CreateFileTest.php @@ -173,6 +173,47 @@ public function testInvalidNonce(){ } + /** + * Test that an invalid nonce prevents creation + * + * @since 1.8.0 + * + * @covers \calderawp\calderaforms\cf2\RestApi\File\CreateFile::createItem() + * @covers \calderawp\calderaforms\cf2\Fields\Handlers\FileUpload::isAllowedType() + * @covers \calderawp\calderaforms\cf2\Fields\Handlers\FileUpload::processFiles() + * + * @group cf2 + * @group file + * @group field + * @group cf2_file + */ + public function testInvalidFileType(){ + wp_set_current_user(1 ); + $formId = 'cf2_file'; + $fieldId = 'cf2_file_3'; + $form = \Caldera_Forms_Forms::get_form( $formId ); + $control = \Caldera_Forms_Field_Util::generate_file_field_unique_id( + \Caldera_Forms_Field_Util::get_field($fieldId,$form), + $form + ); + $nonce = \Caldera_Forms_Render_Nonce::create_verify_nonce($formId ); + + $request = $this->createFileRequest($nonce,$formId,$control,$fieldId); + + $request->set_param('verify', $nonce); + $request->set_param('formId', $formId); + $request->set_param('control', $control); + $request->set_param('fieldId', $fieldId); + $request->set_param('hashes', [ + 0 => md5_file($this->test_file) + ]); + + $response = rest_get_server()->dispatch($request); + $this->assertEquals(415, $response->get_status()); + + } + + /** * Test that the transient with file data is set for next request. * diff --git a/tests/Unit/ExceptionTest.php b/tests/Unit/ExceptionTest.php new file mode 100644 index 000000000..b0f25abff --- /dev/null +++ b/tests/Unit/ExceptionTest.php @@ -0,0 +1,66 @@ +assertInstanceOf( + 'WP_Error', + (new Exception(500)) + ->toWpError([]) + ); + + + } + + /** + * @covers \calderawp\calderaforms\cf2\Exception::formOtherException() + */ + public function testFormOtherException() + { + $code = 500; + $message = 'fail'; + $data = [1 => 2]; + $original = new \Exception($message,$code); + $e = Exception::formOtherException($original); + $this->assertEquals($code,$e->getCode()); + $this->assertEquals($message,$e->getMessage()); + } + + /** + * @since 1.8.0 + * + * @group Exception + * @group cf2 + * + * @covers \calderawp\calderaforms\cf2\Exception::fromWpError() + */ + public function testFromWpError() + { + $code = 500; + $message = 'fail'; + $wpError = \Mockery::mock('WP_Error'); + $wpError->shouldReceive( 'get_error_code' ) + ->andReturn( $code ); + $wpError->shouldReceive( 'get_error_message' ) + ->andReturn( $message ); + + $e = Exception::fromWpError($wpError); + $this->assertEquals($code,$e->getCode()); + $this->assertEquals($message,$e->getMessage()); + + } + + + +} \ No newline at end of file diff --git a/tests/Util/Mocks/MockUploader.php b/tests/Util/Mocks/MockUploader.php index e7f70c709..1d8658b7a 100644 --- a/tests/Util/Mocks/MockUploader.php +++ b/tests/Util/Mocks/MockUploader.php @@ -12,4 +12,13 @@ public function upload($file, array $args = array()) // TODO: Implement upload() method. } + public function removeFilter() + { + // TODO: Implement removeFilter() method. + } + + public function addFilter($fieldId, $formId, $private) + { + // TODO: Implement addFilter() method. + } } \ No newline at end of file diff --git a/tests/includes/forms/cf2-file-include.php b/tests/includes/forms/cf2-file-include.php index 9ea5ff977..be2630b65 100644 --- a/tests/includes/forms/cf2-file-include.php +++ b/tests/includes/forms/cf2-file-include.php @@ -86,7 +86,28 @@ function slug_register_caldera_forms_cf2file( $forms ) { array( 'custom_class' => '', 'multi_upload_text' => '', - 'allowed' => '', + 'allowed' => 'png,jpg', + 'email_identifier' => 0, + 'personally_identifying' => 0, + 'media_lib' => true + ), + ), + 'cf2_file_3' => + array( + 'ID' => 'cf2_file_3', + 'type' => 'cf2_file', + 'label' => 'PNG ONLY', + 'slug' => 'cf2_file_3', + 'conditions' => + array( + 'type' => '', + ), + 'caption' => '', + 'config' => + array( + 'custom_class' => '', + 'multi_upload_text' => '', + 'allowed' => 'png', 'email_identifier' => 0, 'personally_identifying' => 0, 'media_lib' => true