Match the cover and logo upload filter to the dp-upload contract
Some checks are pending
DevPlace CI / test (pull_request) Blocked by required conditions

The cover and logo widgets declared allowed-types as bare extensions
(png,jpg,jpeg,gif,webp), but dp-upload builds the candidate extension
with a leading dot before testing membership, so every selected file
was refused with "type is not allowed". The four widgets were the only
hardcoded lists in the codebase: every other call site passes
allowed_file_types(), which defaults to empty and therefore disables
the client filter entirely, which is why nothing else exposed the
mismatch.

Rather than dotting a duplicated literal in four places, the effective
list now comes from a new allowed_image_types() Jinja global that
intersects allowed_extensions() with IMAGE_EXTENSIONS. That reuses the
one server-side choke point, so the widget can never advertise a type
the upload gate would reject, and narrowing the admin allowed_file_types
setting narrows these widgets with it. IMAGE_EXTENSIONS rather than
POST_IMAGE_EXTENSIONS is the correct set here because the route guard
is _hero_attachment_uid, which accepts any is_image attachment, and
bmp/tiff both upload and pass it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
blindxfish 2026-08-13 08:29:35 +02:00
parent 72e088c160
commit 265cb781f9
3 changed files with 15 additions and 5 deletions

View File

@ -295,11 +295,11 @@
<div class="grid-2col"> <div class="grid-2col">
<div class="auth-field"> <div class="auth-field">
<label>Cover image{% if cover %} (replaces current){% endif %}</label> <label>Cover image{% if cover %} (replaces current){% endif %}</label>
<dp-upload name="cover_attachment_uid" max-files="1" accept="image/*" allowed-types="png,jpg,jpeg,gif,webp" max-size="{{ max_upload_size_mb() }}" label="Upload cover"></dp-upload> <dp-upload name="cover_attachment_uid" max-files="1" accept="image/*" allowed-types="{{ allowed_image_types() }}" max-size="{{ max_upload_size_mb() }}" label="Upload cover"></dp-upload>
</div> </div>
<div class="auth-field"> <div class="auth-field">
<label>Project logo{% if logo %} (replaces current){% endif %}</label> <label>Project logo{% if logo %} (replaces current){% endif %}</label>
<dp-upload name="logo_attachment_uid" max-files="1" accept="image/*" allowed-types="png,jpg,jpeg,gif,webp" max-size="{{ max_upload_size_mb() }}" label="Upload logo"></dp-upload> <dp-upload name="logo_attachment_uid" max-files="1" accept="image/*" allowed-types="{{ allowed_image_types() }}" max-size="{{ max_upload_size_mb() }}" label="Upload logo"></dp-upload>
</div> </div>
</div> </div>

View File

@ -140,11 +140,11 @@
<div class="grid-2col"> <div class="grid-2col">
<div class="auth-field"> <div class="auth-field">
<label>Cover image (optional)</label> <label>Cover image (optional)</label>
<dp-upload name="cover_attachment_uid" max-files="1" accept="image/*" allowed-types="png,jpg,jpeg,gif,webp" max-size="{{ max_upload_size_mb() }}" label="Upload cover"></dp-upload> <dp-upload name="cover_attachment_uid" max-files="1" accept="image/*" allowed-types="{{ allowed_image_types() }}" max-size="{{ max_upload_size_mb() }}" label="Upload cover"></dp-upload>
</div> </div>
<div class="auth-field"> <div class="auth-field">
<label>Project logo (optional)</label> <label>Project logo (optional)</label>
<dp-upload name="logo_attachment_uid" max-files="1" accept="image/*" allowed-types="png,jpg,jpeg,gif,webp" max-size="{{ max_upload_size_mb() }}" label="Upload logo"></dp-upload> <dp-upload name="logo_attachment_uid" max-files="1" accept="image/*" allowed-types="{{ allowed_image_types() }}" max-size="{{ max_upload_size_mb() }}" label="Upload logo"></dp-upload>
</div> </div>
</div> </div>

View File

@ -13,7 +13,12 @@ from devplacepy.avatar import avatar_url, avatar_seed
from devplacepy.utils import format_date as _format_date from devplacepy.utils import format_date as _format_date
from devplacepy.utils import time_ago as _time_ago from devplacepy.utils import time_ago as _time_ago
from devplacepy.utils import get_badge, is_admin, is_primary_admin, pretty_json from devplacepy.utils import get_badge, is_admin, is_primary_admin, pretty_json
from devplacepy.attachments import format_file_size, file_icon_emoji from devplacepy.attachments import (
IMAGE_EXTENSIONS,
allowed_extensions,
format_file_size,
file_icon_emoji,
)
from devplacepy.content import is_owner as _owns from devplacepy.content import is_owner as _owns
from devplacepy.content import maturity_hidden as _maturity_hidden from devplacepy.content import maturity_hidden as _maturity_hidden
from devplacepy.customization import custom_css_tag, custom_js_tag, page_type_for from devplacepy.customization import custom_css_tag, custom_js_tag, page_type_for
@ -224,9 +229,14 @@ def jinja_allowed_file_types() -> str:
return get_setting("allowed_file_types", "") return get_setting("allowed_file_types", "")
def jinja_allowed_image_types() -> str:
return ",".join(sorted(allowed_extensions() & IMAGE_EXTENSIONS))
templates.env.globals["max_upload_size_mb"] = jinja_max_upload_size_mb templates.env.globals["max_upload_size_mb"] = jinja_max_upload_size_mb
templates.env.globals["max_attachments_per_resource"] = jinja_max_attachments templates.env.globals["max_attachments_per_resource"] = jinja_max_attachments
templates.env.globals["allowed_file_types"] = jinja_allowed_file_types templates.env.globals["allowed_file_types"] = jinja_allowed_file_types
templates.env.globals["allowed_image_types"] = jinja_allowed_image_types
_LANGUAGE_NAMES = { _LANGUAGE_NAMES = {
"python": "Python", "python": "Python",