diff --git a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php index db97169ec4..7f840fb988 100644 --- a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php +++ b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php @@ -45,6 +45,13 @@ class FileUploadResource extends ResourceBase { } /** + * The regex used to extract the filename from the content disposition header. + * + * @var string + */ + const FILENAME_REGEX = '@\bfilename=\"(?.+)\"@'; + + /** * @var \Drupal\Core\File\FileSystem */ protected $fileSystem; @@ -139,7 +146,16 @@ public static function create(ContainerInterface $container, array $configuratio * Creates a file from endpoint. * * @param \Symfony\Component\HttpFoundation\Request $request - * @param \Drupal\file\FileInterface $file + * The current request. + * @param string $entity_type_id + * The entity type ID. + * @param string $bundle + * The bundle. + * @param string $field_name + * The field name. + * + * @return \Drupal\rest\ModifiedResourceResponse + * A 201 response, on success. */ public function post(Request $request, $entity_type_id, $bundle, $field_name) { $filename = $this->validateAndParseContentDispositionHeader($request); @@ -222,16 +238,18 @@ protected function streamUploadData($destination_uri) { /** * Validates and extracts the filename from the Content-Disposition header. * - * @param string $content_disposition + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object. * * @return string + * The filename extracted from the header. * * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException */ protected function validateAndParseContentDispositionHeader(Request $request) { // Firstly, check the header exists. if (!$request->headers->has('content-disposition')) { - throw new BadRequestHttpException('"Content-Disposition" header is required. A file name in the format "filename=FILENAME" should be provided'); + throw new BadRequestHttpException('"Content-Disposition" header is required. A file name in the format "filename=FILENAME" must be provided'); } $content_disposition = $request->headers->get('content-disposition'); @@ -239,8 +257,8 @@ protected function validateAndParseContentDispositionHeader(Request $request) { // Parse the header value. This regex does not allow an empty filename. // i.e. 'filename=""'. This also matches on a word boundary so other keys // like 'not_a_filename' don't work. - if (!preg_match('@\bfilename=\"(?.+)\"@', $content_disposition, $matches)) { - throw new BadRequestHttpException('No filename found in "Content-Disposition" header. A file name in the format "filename=FILENAME" should be provided'); + if (!preg_match(static::FILENAME_REGEX, $content_disposition, $matches)) { + throw new BadRequestHttpException('No filename found in "Content-Disposition" header. A file name in the format "filename=FILENAME" must be provided'); } // Don't validate the actual filename here, that will be done by the upload @@ -314,7 +332,7 @@ protected function validate(FileInterface $file, FieldDefinitionInterface $field * The array of field settings. * * @return string - * An unsanitized file directory URI with tokens replaced. The result of + * An un-sanitized file directory URI with tokens replaced. The result of * the token replacement is then converted to plain text and returned. */ protected function getUploadLocation(array $settings) { @@ -388,6 +406,7 @@ protected function getBaseRouteRequirements($method) { // incoming requests can only use the 'application/octet-stream' // Content-Type header. $requirements['_content_type_format'] = 'bin'; + // Allow all serializer formats to be returned as a response format. $requirements['_format'] = implode('|', $this->serializerFormats); return $requirements; diff --git a/core/modules/hal/tests/src/Functional/EntityResource/File/FileUploadHalJsonTestBase.php b/core/modules/hal/tests/src/Functional/EntityResource/File/FileUploadHalJsonTestBase.php index 94ddb67ab1..d1e65730a7 100644 --- a/core/modules/hal/tests/src/Functional/EntityResource/File/FileUploadHalJsonTestBase.php +++ b/core/modules/hal/tests/src/Functional/EntityResource/File/FileUploadHalJsonTestBase.php @@ -31,11 +31,10 @@ protected function getExpectedNormalizedEntity() { $normalization = parent::getExpectedNormalizedEntity(); - // @todo Look into why apply applyHalFieldNormalization() doesn't work here. - // - It does not remove the uid property - // - It add an 'en' langcode to the created property only (suggesting it - // thinks just that field is translatable). - // - The file URL needs to be created anyway AFAICT. + // Cannot use applyHalFieldNormalization() as it uses the $entity property + // from the test class, which in the case of file upload tests, is the + // parent entity test entity for the file that's created. + // Hal uses the full file URL for the URI field. $normalization['uri'][0]['value'] = file_create_url($normalization['uri'][0]['value']); // Hal adds reference fields in links and embedded. @@ -44,7 +43,7 @@ protected function getExpectedNormalizedEntity() { return $normalization + [ '_links' => [ 'self' => [ - 'href' => file_create_url('public://foobar/example.txt'), + 'href' => $normalization['uri'][0]['value'], ], 'type' => [ 'href' => $this->baseUrl . '/rest/type/file/file', diff --git a/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php b/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php index c3d653bbdb..b6cb7c04c7 100644 --- a/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php @@ -130,16 +130,18 @@ public function testPostFileUpload() { $response = $this->fileRequest($uri, $this->testFileData, ['Content-Disposition' => '']); $this->assertSame(400, $response->getStatusCode()); - // An empty filename in the Content-Disposition header should return a 400. + // An empty filename with a context in the Content-Disposition header should + // return a 400. $response = $this->fileRequest($uri, $this->testFileData, ['Content-Disposition' => 'file; filename=""']); $this->assertSame(400, $response->getStatusCode()); - // An empty filename in the Content-Disposition header should return a 400. + // An empty filename without a context in the Content-Disposition header + // should return a 400. $response = $this->fileRequest($uri, $this->testFileData, ['Content-Disposition' => 'filename=""']); $this->assertSame(400, $response->getStatusCode()); - // Another key-value pair in the Content-Disposition header should return a - // 400. + // An invalid key-value pair in the Content-Disposition header should return + // a 400. $response = $this->fileRequest($uri, $this->testFileData, ['Content-Disposition' => 'not_a_filename="example.txt"']); $this->assertSame(400, $response->getStatusCode()); @@ -253,8 +255,6 @@ protected function getExpectedNormalizedEntity() { * @param array $headers * Additional headers to send with the request. Defaults will be added for * Content-Type and Content-Disposition. - * @param string $content_type - * The content type header to send in the file request. * * @return \Psr\Http\Message\ResponseInterface */