diff --git a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php index 3ae7a44429..5446d0b8b7 100644 --- a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php +++ b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php @@ -20,7 +20,7 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; -use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; +use Symfony\Component\Routing\Route; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -135,7 +135,7 @@ public function post(Request $request, $entity_type_id, $bundle, $field_name) { // @todo Validate for file name too? $field_definition = $this->validateAndLoadFieldDefinition($entity_type_id, $bundle, $field_name); - $destination = $this->getUploadLocation($field_definition->getFieldStorageDefinition()->getSettings()); + $destination = $this->getUploadLocation($field_definition->getSettings()); // Grab the filename as described in Content-Disposition header. $content_disposition = $request->headers->get('Content-Disposition'); @@ -152,7 +152,7 @@ public function post(Request $request, $entity_type_id, $bundle, $field_name) { } // Create the file. - $file_uri = "{$destination}/{$filename}"; + $file_uri = "{$destination}{$filename}"; $this->streamUploadData($file_uri); @@ -332,6 +332,22 @@ protected function getUploadValidators($field_definition) { /** * {@inheritdoc} */ + protected function getBaseRoute($canonical_path, $method) { + return new Route($canonical_path, [ + '_controller' => 'Drupal\rest\RequestHandler::handleRaw', + ], + $this->getBaseRouteRequirements($method), + [], + '', + [], + // The HTTP method is a requirement for this route. + [$method] + ); + } + + /** + * {@inheritdoc} + */ protected function getBaseRouteRequirements($method) { $requirements = parent::getBaseRouteRequirements($method); diff --git a/core/modules/file/tests/src/Functional/FileUploadResourceTestBase.php b/core/modules/file/tests/src/Functional/FileUploadResourceTestBase.php index 15456c1603..f1a82041c7 100644 --- a/core/modules/file/tests/src/Functional/FileUploadResourceTestBase.php +++ b/core/modules/file/tests/src/Functional/FileUploadResourceTestBase.php @@ -71,6 +71,9 @@ public function setUp() { 'entity_type' => 'entity_test', 'field_name' => 'field_rest_file_test', 'type' => 'file', + 'settings' => [ + 'uri_scheme' => 'public', + ], ]) ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) ->save(); @@ -79,6 +82,11 @@ public function setUp() { 'entity_type' => 'entity_test', 'field_name' => 'field_rest_file_test', 'bundle' => 'entity_test', + 'settings' => [ + 'file_directory' => '', + 'file_extensions' => 'txt', + 'max_filesize' => '', + ], ]) ->setLabel('Test file field') ->setTranslatable(FALSE) @@ -104,7 +112,7 @@ protected function setUpAuthorization($method) { $this->grantPermissionsToTestedRole(['view test entity']); break; case 'POST': - $this->grantPermissionsToTestedRole(['create entity_test entity_test entities']); + $this->grantPermissionsToTestedRole(['create entity_test entity_test entities', 'restful post file:upload']); break; } } @@ -193,7 +201,7 @@ protected function fileRequest(Url $url, $file_contents, $content_type = 'applic $request_options[RequestOptions::HTTP_ERRORS] = FALSE; $request_options['headers']['Content-Type'] = $content_type; // @todo change when we settle on the header for the file name. - $request_options['headers']['Content-Disposition'] = 'file; filename=example.txt'; + $request_options['headers']['Content-Disposition'] = 'file; filename="example.txt"'; $request_options['body'] = $file_contents; $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('POST')); diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index e5437ccb68..6731c26d0c 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -139,4 +139,47 @@ public function handle(RouteMatchInterface $route_match, Request $request) { return $response; } + public function handleRaw(RouteMatchInterface $route_match, Request $request) { + // Symfony is built to transparently map HEAD requests to a GET request. In + // the case of the REST module's RequestHandler though, we essentially have + // our own light-weight routing system on top of the Drupal/symfony routing + // system. So, we have to respect the decision that the routing system made: + // we look not at the request method, but at the route's method. All REST + // routes are guaranteed to have _method set. + // Response::prepare() will transform it to a HEAD response at the very last + // moment. + // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 + // @see \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection() + // @see \Symfony\Component\HttpFoundation\Response::prepare() + $method = strtolower($route_match->getRouteObject()->getMethods()[0]); + assert(count($route_match->getRouteObject()->getMethods()) === 1); + + + $resource_config_id = $route_match->getRouteObject()->getDefault('_rest_resource_config'); + /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */ + $resource_config = $this->resourceStorage->load($resource_config_id); + $resource = $resource_config->getResourcePlugin(); + + // Determine the request parameters that should be passed to the resource + // plugin. + $route_parameters = $route_match->getParameters(); + $parameters = []; + // Filter out all internal parameters starting with "_". + foreach ($route_parameters as $key => $parameter) { + if ($key{0} !== '_') { + $parameters[] = $parameter; + } + } + + // Invoke the operation on the resource plugin. + $response = call_user_func_array([$resource, $method], array_merge([$request], $parameters)); + + if ($response instanceof CacheableResponseInterface) { + // Add rest config's cache tags. + $response->addCacheableDependency($resource_config); + } + + return $response; + } + }