Thomas Winged

Why do images are downloaded instead of opened in a new tab?


After releasing my last post about the development of wheel pawn for the Wheel of Fortune game, I encountered a problem: right-clicking on images on my website and clicking Open in New Tab triggered downloading them instead of opening in a new tab.

I researched the internet and found out it's a problem with MIME. But what is MIME? I had to do another research, and I found a helpful description on Wikipedia:

"MIME is an Internet standard that extends the format of email messages to support text in character sets other than ASCII, as well as attachments of audio, video, images, and application programs. (...) In the HTTP, servers insert a MIME header field at the beginning of any Web transmission. Clients use the content type or media type header to select an appropriate viewer application for the type of data indicated."

Source: Wikipedia

That made sense - some MIME information was incorrectly set on the server side, and the browser needed help understanding what to do with the requested file. So I headed to AWS S3 and searched where the header metadata information is stored per object. After a short search, I found it, and in fact, it said binary/octet-stream:

221205092817-Yg1CQGou27.webp

Metadata stating that this object should be interpreted as binary/octet-stream

I looked online to find appropriate MIME for webp images and replaced it with image/webp. Effect? Image opens in new tab as it supposed to!

But now is the question - why these images became interpreted as binary/octet-stream in the first place? Meanwhile, I noticed that uploading files directly from the browser to AWS S3 correctly sets MIME for the uploaded file. And so because I use Python with Flask as the backend of my website, I looked into the documentation of boto3, which I use for managing assets on AWS. This is what I found:

"Both upload_file and upload_fileobj accept an optional ExtraArgs parameter that can be used for various purposes. (...) The following ExtraArgs setting specifies metadata to attach to the S3 object."

Source: Boto3 Docs

So I headed to the place where I use boto3 to upload assets of an entry and modified it like so:

aws_helper.py - mapping file extension to content type and using upload_file
def upload_file(slug: str, filepath: str) -> str:
    ...

    extension = os.path.splitext(filepath)[1]
    ext_mime = {
        '.webp': 'image/webp',
        '.webm': 'video/webm'
    }
    if extension in ext_mime.keys():
        content_type = ext_mime[extension]
    else:
        content_type = 'binary/octet-stream'

    response = (
        boto3.resource('s3', region_name=REGION)
        .Bucket(BUCKET)
        .upload_file(filepath, key, ExtraArgs={'ContentType': content_type})
    )

    ...

Note: put_object() method requires passing MIME directly to ContentType attribute, hence there is a slight difference:

aws_helper.py - using put_object method differs a little
boto3.resource('s3', region_name=REGION)
.Bucket(BUCKET)
.put_object(
  Body=obj, Key=key, ContentType=content_type
)

Not the most elegant solution, but it works (right-click on the image above and see for yourself). So the lesson I learned is that if an operation related to opening content in a browser misbehaves, double-check if the MIME property was set correctly.

I hope that this short post will help somebody struggling with the same problem. Peace! : )