fix(uploads): register HEIC/HEIF with the file validator + fix admin format hint (codex review of #832)
Two findings from the Codex review:
- validateFileType requires an ALLOWED_MEDIA_TYPES entry, which had neither
image/heic nor image/heif — so HEIC was rejected before sharp ever saw it,
despite the EXTENSION_TO_MIME additions. Added both with a single 'ftyp'
(offset 4) magic number (the check is .every, so alternatives can't be
separate entries).
- Changing the shared upload.fileRequirements string to interpolate {{formats}}
left the admin PhotoUpload caller passing only { limit }, rendering the
placeholder literally (it was also already dropping {{sizeLimit}} from #823).
The admin caller now passes formats + sizeLimit + limit, from the admin
settings it already loads.
This commit is contained in:
@@ -79,6 +79,22 @@ const ALLOWED_IMAGE_TYPES = {
|
|||||||
extensions: ['.svg'],
|
extensions: ['.svg'],
|
||||||
// SVG files are XML-based text files, so we skip magic number validation
|
// SVG files are XML-based text files, so we skip magic number validation
|
||||||
magicNumbers: null
|
magicNumbers: null
|
||||||
|
},
|
||||||
|
// HEIC/HEIF (iPhone). ISO-BMFF container: bytes 4-7 are the "ftyp" box marker,
|
||||||
|
// present in every HEIF/HEIC file (single entry — the magic check is `.every`,
|
||||||
|
// so alternatives can't be listed as separate entries). Sharp's libvips
|
||||||
|
// decodes these; extension + MIME are already gated by validateFileType.
|
||||||
|
'image/heic': {
|
||||||
|
extensions: ['.heic'],
|
||||||
|
magicNumbers: [
|
||||||
|
{ offset: 4, bytes: [0x66, 0x74, 0x79, 0x70] } // "ftyp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'image/heif': {
|
||||||
|
extensions: ['.heif'],
|
||||||
|
magicNumbers: [
|
||||||
|
{ offset: 4, bytes: [0x66, 0x74, 0x79, 0x70] } // "ftyp"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { categoriesService } from '../../services/categories.service';
|
import { categoriesService } from '../../services/categories.service';
|
||||||
import { settingsService } from '../../services/settings.service';
|
import { settingsService } from '../../services/settings.service';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { extensionsToMimeTypes, extensionsToAcceptString } from '../../utils/fileTypes';
|
import { extensionsToMimeTypes, extensionsToAcceptString, extensionsToLabel } from '../../utils/fileTypes';
|
||||||
import { useUploadProgress } from '../../hooks/useUploadProgress';
|
import { useUploadProgress } from '../../hooks/useUploadProgress';
|
||||||
|
|
||||||
interface PhotoUploadProps {
|
interface PhotoUploadProps {
|
||||||
@@ -118,6 +118,15 @@ export const PhotoUpload: React.FC<PhotoUploadProps> = ({ eventId, onUploadCompl
|
|||||||
[settings?.general_allowed_file_types]
|
[settings?.general_allowed_file_types]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const formatsLabel = useMemo(
|
||||||
|
() => extensionsToLabel(settings?.general_allowed_file_types),
|
||||||
|
[settings?.general_allowed_file_types]
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxFileSizeMb = Number.isFinite(Number(settings?.general_max_file_size_mb))
|
||||||
|
? Number(settings?.general_max_file_size_mb)
|
||||||
|
: 50;
|
||||||
|
|
||||||
const remainingSlots = Math.max(maxFilesPerUpload - selectedFiles.length, 0);
|
const remainingSlots = Math.max(maxFilesPerUpload - selectedFiles.length, 0);
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
|
|
||||||
@@ -511,7 +520,7 @@ export const PhotoUpload: React.FC<PhotoUploadProps> = ({ eventId, onUploadCompl
|
|||||||
{t('upload.clickToUpload')}
|
{t('upload.clickToUpload')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">
|
<p className="text-sm text-neutral-500 dark:text-neutral-400">
|
||||||
{t('upload.fileRequirements', { limit: maxFilesPerUpload })}
|
{t('upload.fileRequirements', { formats: formatsLabel, limit: maxFilesPerUpload, sizeLimit: maxFileSizeMb })}
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
|||||||
Reference in New Issue
Block a user