dustin10 / VichUploaderBundle

A simple Symfony bundle to ease file uploads with ORM entities and ODM documents.
MIT License
1.84k stars 519 forks source link

The mime type is not correctly passed to the S3 adapter via Flysystem storage class #1223

Open morawskim opened 3 years ago

morawskim commented 3 years ago

Bug Report

Q A
BC Break no
Bundle version 1.18.0
Symfony version 4.4.20
PHP version 7.4

Summary

The mime type is not correctly passed to the S3 adapter via Flysystem storage class (\Vich\UploaderBundle\Storage\FlysystemStorage).

Current behavior

symfony/validator component works well because the image validator can correctly detect file mime type. The problem is with integration between vich/uploader-bundle and league/flysystem-aws-s3-v3. When vich tries to store a file in S3 the method doUpload of class \Vich\UploaderBundle\Storage\FlysystemStorage is called. This method looks like this:

protected function doUpload(PropertyMapping $mapping, UploadedFile $file, ?string $dir, string $name): void
{
    $fs = $this->getFilesystem($mapping);
    $path = !empty($dir) ? $dir.'/'.$name : $name;

    $stream = \fopen($file->getRealPath(), 'rb');
    try {
        $fs->writeStream($path, $stream, [
            'mimetype' => $file->getMimeType(),
        ]);
    } catch (FilesystemException $e) {
        throw new CannotWriteFileException($e->getMessage());
    }
}

In this method, we set option mimetype from file (this is correct because the value will be image/jpg). Because I use an S3 adapter the write method of class \League\Flysystem\AwsS3V3\AwsS3V3Adapter is called. This method is very simple because delegates storing a file to the internal method upload. The source code of this method is below:

private function upload(string $path, $body, Config $config): void
{
    $key = $this->prefixer->prefixPath($path);
    $acl = $this->determineAcl($config);
    $options = $this->createOptionsFromConfig($config);
    $shouldDetermineMimetype = $body !== '' && ! array_key_exists('ContentType', $options);

    if ($shouldDetermineMimetype && $mimeType = $this->mimeTypeDetector->detectMimeType($key, $body)) {
        $options['ContentType'] = $mimeType;
    }

    $this->client->upload($this->bucket, $key, $body, $acl, ['params' => $options]);
}

As you can see S3 adapter expects mime type in key ContentType instead of mimetype. The default implementation of detectMimeType is based on file extension. This is not excatly true (you can check \League\MimeTypeDetection\FinfoMimeTypeDetector::detectMimeType), but this not change a lots.

When I want to fetch\check mime type of a file from S3, I get the wrong mime type.

Maybe we could also set key ContentType in \Vich\UploaderBundle\Storage\FlysystemStorage::doUpload? I can prepare PR if you think that this solution is fine.

How to reproduce

Configure bundle with FlysystemStorage and S3 adapter (you can use Minio as a free and open-source alternative). Change the file extension from jpg to mp4.

Expected behavior

The correct mime-type should be stored in the file's metadata in S3.

garak commented 2 years ago

I guess that this problem is related to the one discussed in #1217

gabplch commented 2 months ago

For all, who will find this issue. The problem is not in Filesystem, but in the method getMimeType() of UploadedFile class. Method will return guessed mime type. If you want one, that you defined earlier, in UploadedFile, use getClientMimeType()

issue can be closed...