Skip to content

MinIO driver uploads files without Content-Type → all files get application/octet-stream #49

@ubredemeier

Description

@ubredemeier

Description

The MinIO driver uploads all media files without setting the content_type
parameter. As a result, MinIO falls back to its default value
application/octet-stream for every object — regardless of whether the file
is a JPEG, PNG, PDF, MP4 or anything else.

This causes problems whenever the bucket is consumed by clients that rely on
the HTTP Content-Type header to render the content inline:

  • Browsers offer the file as a download instead of displaying it
  • QGIS HTML widgets / map tips cannot render <img> references to the bucket
  • Embedding photos in WebGIS / popups / reports requires a workaround
  • The MinIO response additionally includes X-Content-Type-Options: nosniff,
    which prevents clients from guessing the type from the payload

A typical response header for an uploaded JPG currently looks like this:

content-type: application/octet-stream
x-content-type-options: nosniff
content-length: 326330

Even though the file is a perfectly valid JPEG.

Reproduction

  1. Set up media-sync with a MinIO backend (driver: minio)

  2. Sync a Mergin Maps project that contains photos (e.g. captured with the
    Mergin Maps mobile app)

  3. Inspect the response headers of any uploaded file in the bucket, e.g.:

    curl -I https://<minio-host>/<bucket>/fotos/example.jpg
    

    Result: Content-Type: application/octet-stream

Root cause

In drivers.py, the MinioDriver.upload_file() method calls
fput_object() without a content_type:

res = self.client.fput_object(self.bucket, obj_path, src)

The default for that parameter in the MinIO Python SDK is
application/octet-stream, so every object ends up with that type.

Proposed fix

Use Python's standard mimetypes module to derive the type from the file
extension, falling back to the previous default if unknown:

import mimetypes

def upload_file(self, src, obj_path):
    if self.bucket_subpath:
        obj_path = f"{self.bucket_subpath}/{obj_path}"
    try:
        content_type, _ = mimetypes.guess_type(src)
        if content_type is None:
            content_type = "application/octet-stream"
        res = self.client.fput_object(
            self.bucket, obj_path, src, content_type=content_type
        )
        dest = self.base_url + "/" + res.object_name
    except S3Error as e:
        raise DriverError("MinIO driver error: " + str(e))
    return dest

This is a minimal, dependency-free change (mimetypes is part of the Python
standard library) that fixes the issue for all common media file types
produced by Mergin Maps (jpg, jpeg, png, heic, mp4, pdf, …).

I'm happy to open a PR if that helps.

Environment

  • media-sync: latest main
  • Backend: MinIO (S3-compatible)
  • Mergin Maps cloud

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions