fix: 502 on .flv/unknown-extension upload — use guess_type + guard None

ParamValidationError on copy_object after upload — ContentType=None
hit S3. get_content_type("thumbnail2") returned None for a .flv file
because mimetypes.types_map only holds Python's built-in table (no
.flv, .mkv, .opus, .m4v, etc.).

Switch to mimetypes.guess_type, which initializes from the OS mime
database and covers every common format. Keep None as the return for
truly unknown extensions so downstream callers that chain `or
"video/mp4"` still pick the right context-aware fallback.

Guard the two copy_object / mirror_key_async sites in views/product.py
with `or "application/octet-stream"` so even a genuinely unknown
extension can't crash the upload handler again.
This commit is contained in:
russell@unturf.com 2026-04-16 15:45:20 -04:00
parent d527b57b04
commit 806a97baca
2 changed files with 13 additions and 5 deletions

View file

@ -418,10 +418,17 @@ class Product(RBase, Base):
return file_key
def get_content_type(self, file_key):
"""return the content type string for the S3 object, or None."""
"""Return the content type string for the S3 object, or None.
Uses mimetypes.guess_type which initializes from the OS mime
database types_map alone only knows Python's built-in table
(no .flv, .mkv, .opus, etc.) and returns None for those.
"""
extension = self.extensions.get(file_key)
if extension:
return mimetypes.types_map.get("." + extension)
if not extension:
return None
content_type, _ = mimetypes.guess_type(f"x.{extension}")
return content_type
def get_content_disposition(self, file_key):
"""

View file

@ -356,6 +356,7 @@ def product_edit(request):
# copy upload to our system defined s3 location.
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.copy_object
content_type = product.get_content_type(file_key) or "application/octet-stream"
request.shop_uploads_client.copy_object(
ACL=acl,
Bucket=request.shop_bucket_name,
@ -364,7 +365,7 @@ def product_edit(request):
"Key": s3_webhook_key,
},
ContentDisposition=product.get_content_disposition(file_key),
ContentType=product.get_content_type(file_key),
ContentType=content_type,
CacheControl=cache_control,
Key=f"{product.s3_path}/{file_key}",
MetadataDirective="REPLACE",
@ -377,7 +378,7 @@ def product_edit(request):
request.shop_bucket_name,
f"{product.s3_path}/{file_key}",
product.shop,
content_type=product.get_content_type(file_key),
content_type=content_type,
cache_control=cache_control,
)