diff --git a/core/modules/file/file.module b/core/modules/file/file.module index be1e136958..b4bbd11601 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -19,6 +19,11 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Template\Attribute; +/** + * The regex pattern used when checking for insecure file types. + */ +define('FILE_INSECURE_EXTENSION_REGEX', '/\.(php|pl|py|cgi|asp|js)(\.|$)/i'); + // Load all Field module hooks for File. require_once __DIR__ . '/file.field.inc'; @@ -954,7 +959,7 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL // rename filename.php.foo and filename.php to filename.php.foo.txt and // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' // evaluates to TRUE. - if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) { + if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match(FILE_INSECURE_EXTENSION_REGEX, $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) { $file->setMimeType('text/plain'); // The destination filename will also later be used to create the URI. $file->setFilename($file->getFilename() . '.txt'); diff --git a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php index 50679a101f..46e1d04999 100644 --- a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php +++ b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php @@ -23,6 +23,7 @@ use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use Symfony\Component\Routing\Route; use Symfony\Component\HttpFoundation\Request; @@ -61,6 +62,13 @@ class FileUploadResource extends ResourceBase { */ const REQUEST_HEADER_FILENAME_REGEX = '@\bfilename(?\*?)=\"(?.+)\"@'; + /** + * The amount of bytes to read in each iteration when streaming file data. + * + * @var int + */ + const BYTES_TO_READ = 8192; + /** * The file system service. * @@ -181,14 +189,15 @@ public function permissions() { } /** - * Creates a file from endpoint. + * Creates a file from an endpoint. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request. * @param string $entity_type_id * The entity type ID. * @param string $bundle - * The bundle. + * The entity bundle. This will be the same as $entity_type_id for entity + * types that don't support bundles. * @param string $field_name * The field name. * @@ -281,7 +290,7 @@ protected function streamUploadData() { if ($temp_file) { while (!feof($file_data)) { - $read = fread($file_data, 8192); + $read = fread($file_data, static::BYTES_TO_READ); if ($read === FALSE) { // Close the file streams. @@ -346,7 +355,7 @@ protected function validateAndParseContentDispositionHeader(Request $request) { // Check for the "filename*" format. This is currently unsupported. if (!empty($matches['star'])) { - throw new BadRequestHttpException('The extended "filename*" format is currently not supported in the "Content-Disposition header"'); + throw new BadRequestHttpException('The extended "filename*" format is currently not supported in the "Content-Disposition" header'); } // Don't validate the actual filename here, that will be done by the upload @@ -380,7 +389,7 @@ protected function validateAndParseContentDispositionHeader(Request $request) { protected function validateAndLoadFieldDefinition($entity_type_id, $bundle, $field_name) { $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle); if (!isset($field_definitions[$field_name])) { - throw new BadRequestHttpException(sprintf('Field "%s" does not exist', $field_name)); + throw new NotFoundHttpException(sprintf('Field "%s" does not exist', $field_name)); } /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */ @@ -454,7 +463,7 @@ protected function prepareFilename($filename, array &$validators) { // rename filename.php.foo and filename.php to filename.php.foo.txt and // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' // evaluates to TRUE. - if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $filename) && (substr($filename, -4) != '.txt')) { + if (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match(FILE_INSECURE_EXTENSION_REGEX, $filename) && (substr($filename, -4) != '.txt')) { // The destination filename will also later be used to create the URI. $filename .= '.txt'; @@ -563,7 +572,7 @@ protected function getBaseRouteRequirements($method) { * @return string * The generated lock ID. */ - protected function generateLockIdFromFileUri($file_uri) { + protected static function generateLockIdFromFileUri($file_uri) { return 'file:rest:' . Crypt::hashBase64($file_uri); } diff --git a/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php b/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php index ef9b5ce7c3..f06a8caa7d 100644 --- a/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/FileUploadResourceTestBase.php @@ -163,6 +163,11 @@ public function testPostFileUpload() { $this->setUpAuthorization('POST'); + // 404 when the field name is invalid. + $invalid_uri = Url::fromUri('base:file/upload/entity_test/entity_test/field_rest_file_test_invalid'); + $response = $this->fileRequest($invalid_uri, $this->testFileData); + $this->assertResourceErrorResponse(404, 'Field "field_rest_file_test_invalid" does not exist', $response); + // This request will have the default 'application/octet-stream' content // type header. $response = $this->fileRequest($uri, $this->testFileData); @@ -274,7 +279,7 @@ public function testPostFileUploadInvalidHeaders() { // Using filename* extended format is not currently supported. $response = $this->fileRequest($uri, $this->testFileData, ['Content-Disposition' => 'filename*="UTF-8 \' \' example.txt"']); - $this->assertResourceErrorResponse(400, 'The extended "filename*" format is currently not supported in the "Content-Disposition header"', $response); + $this->assertResourceErrorResponse(400, 'The extended "filename*" format is currently not supported in the "Content-Disposition" header', $response); } /**