diff --git a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php index 1a954e5b40..1177f9a961 100644 --- a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php +++ b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php @@ -3,6 +3,10 @@ namespace Drupal\file\Plugin\rest\resource; use Drupal\Component\Plugin\DependentPluginInterface; +use Drupal\Component\Utility\Bytes; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\file\FileInterface; use Drupal\rest\ModifiedResourceResponse; use Drupal\rest\Plugin\ResourceBase; use Drupal\Component\Render\PlainTextOutput; @@ -13,7 +17,9 @@ use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\HttpFoundation\Request; @@ -52,6 +58,16 @@ class FileUploadResource extends ResourceBase implements DependentPluginInterfac protected $entityFieldManager; /** + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * @var \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface + */ + protected $mimeTypeGuesser; + + /** * Constructs a FileUploadResource instance. * * @param array $configuration @@ -68,12 +84,18 @@ class FileUploadResource extends ResourceBase implements DependentPluginInterfac * The file system service. * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager * The entity field manager. + * @param \Drupal\Core\Session\AccountInterface $current_user + * The currently authenticated user. + * @param \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $mime_type_guesser + * The MIME type guesser. * */ - public function __construct(array $configuration, $plugin_id, $plugin_definition,$serializer_formats, LoggerInterface $logger, FileSystem $file_system, EntityFieldManagerInterface $entity_field_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition,$serializer_formats, LoggerInterface $logger, FileSystem $file_system, EntityFieldManagerInterface $entity_field_manager, AccountInterface $current_user, MimeTypeGuesserInterface $mime_type_guesser) { parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger); $this->fileSystem = $file_system; $this->entityFieldManager = $entity_field_manager; + $this->currentUser = $current_user; + $this->mimeTypeGuesser = $mime_type_guesser; } /** @@ -87,7 +109,9 @@ public static function create(ContainerInterface $container, array $configuratio $container->getParameter('serializer.formats'), $container->get('logger.factory')->get('rest'), $container->get('file_system'), - $container->get('entity_field.manager') + $container->get('entity_field.manager'), + $container->get('current_user'), + $container->get('file.mime_type.guesser') ); } @@ -143,15 +167,26 @@ public function post(Request $request, $entity_type_id, $bundle, $field_name) { // Create the file. $file_uri = "{$destination}/{$filename}"; - $file = File::create([ + $this->streamUploadData($file_uri); + + // Begin building file entity. + $values = [ + 'uid' => $this->currentUser->id(), + 'status' => 0, + 'filename' => $filename, 'uri' => $file_uri, - ]); + // Set the size. This is done in File::preSave() but we validate the file + // before it is saved. + 'size' => @filesize($file_uri), + ]; + $values['filemime'] = $this->mimeTypeGuesser->guess($values['filename']); + $file = File::create($values); - $this->streamUploadData($file_uri); + $this->validateFile($file, $field_definition); - // @todo Also validate based on the above field definition. $this->validate($file); + $file->save(); return new ModifiedResourceResponse($file, 201); @@ -206,6 +241,28 @@ protected function validateOctetStream(Request $request) { } /** + * Validates the file. + * + * @param \Drupal\file\FileInterface $file + * The file entity to validate. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition to validate against. + * + * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException + */ + protected function validateFile(FileInterface $file, FieldDefinitionInterface $field_definition) { + // Validate the file based on the field definition configuration. + $errors = file_validate($file, $this->getUploadValidators($field_definition)); + + if (!empty($errors)) { + $message = "Unprocessable Entity: file validation failed.\n"; + $message .= implode("\n", $errors); + + throw new UnprocessableEntityHttpException($message); + } + } + + /** * Determines the URI for a file field. * * @param array $settings @@ -224,4 +281,40 @@ protected function getUploadLocation(array $settings) { return $settings['uri_scheme'] . '://' . $destination; } + /** + * Retrieves the upload validators for a field definition. + * + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * + * @return array + * An array suitable for passing to file_save_upload() or the file field + * element's '#upload_validators' property. + * + * This is copied from \Drupal\file\Plugin\Field\FieldType\FileItem as there + * is no entity instance available here that that a FileItem would exist for. + */ + protected function getUploadValidators($field_definition) { + $validators = [ + // Add in our check of the file name length. + 'file_validate_name_length' => [], + ]; + $settings = $field_definition->getSettings(); + + // Cap the upload size according to the PHP limit. + $max_filesize = Bytes::toInt(file_upload_max_size()); + if (!empty($settings['max_filesize'])) { + $max_filesize = min($max_filesize, Bytes::toInt($settings['max_filesize'])); + } + + // There is always a file size limit due to the PHP server limit. + $validators['file_validate_size'] = [$max_filesize]; + + // Add the extension check if necessary. + if (!empty($settings['file_extensions'])) { + $validators['file_validate_extensions'] = [$settings['file_extensions']]; + } + + return $validators; + } + }