From 4486f02bc69fee0668b914ca89f0d82756da8b70 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Sun, 18 Oct 2026 00:09:14 -0400 Subject: [PATCH] fix(privacy): "Remove Photo Metadata" also covers emoji/sticker packs and badges (#332) Custom emoji and sticker images, pack avatars and power-level badge images were uploaded with their EXIF intact (GPS location, timestamp), although they are published to every member of the room or space. Route those three pickers through a new useStrippedFilePicker, which applies the same stripImageMetadata as attachments and avatars. Fixes #332 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- .../image-pack-view/ImagePackContent.tsx | 4 +-- .../components/image-pack-view/PackMeta.tsx | 4 +-- .../permissions/PowersEditor.tsx | 4 +-- src/app/hooks/useStrippedFilePicker.ts | 36 +++++++++++++++++++ 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 src/app/hooks/useStrippedFilePicker.ts diff --git a/src/app/components/image-pack-view/ImagePackContent.tsx b/src/app/components/image-pack-view/ImagePackContent.tsx index 9be2e9a58..47c283668 100644 --- a/src/app/components/image-pack-view/ImagePackContent.tsx +++ b/src/app/components/image-pack-view/ImagePackContent.tsx @@ -16,7 +16,7 @@ import { SettingTile } from '../setting-tile'; import { UsageSwitcher } from './UsageSwitcher'; import { ImagePackProfile, ImagePackProfileEdit } from './PackMeta'; import * as css from './style.css'; -import { useFilePicker } from '../../hooks/useFilePicker'; +import { useStrippedFilePicker } from '../../hooks/useStrippedFilePicker'; import { CompactUploadCardRenderer } from '../upload-card'; import { UploadSuccess } from '../../state/upload'; import { getImageInfo, TUploadContent } from '../../utils/matrix'; @@ -60,7 +60,7 @@ export const ImagePackContent = as<'div', ImagePackContentProps>( [imagePack, savedImages, uploadedImages], ); - const pickFiles = useFilePicker( + const pickFiles = useStrippedFilePicker( useCallback( (pickedFiles: File[]) => { const uniqueFiles = pickedFiles.map((file) => { diff --git a/src/app/components/image-pack-view/PackMeta.tsx b/src/app/components/image-pack-view/PackMeta.tsx index e04169d4a..d79aa84b6 100644 --- a/src/app/components/image-pack-view/PackMeta.tsx +++ b/src/app/components/image-pack-view/PackMeta.tsx @@ -19,7 +19,7 @@ import { nameInitials } from '../../utils/common'; import { BreakWord } from '../../styles/Text.css'; import { LINKIFY_OPTS } from '../../plugins/react-custom-html-parser'; import { ContainerColor } from '../../styles/ContainerColor.css'; -import { useFilePicker } from '../../hooks/useFilePicker'; +import { useStrippedFilePicker } from '../../hooks/useStrippedFilePicker'; import { useObjectURL } from '../../hooks/useObjectURL'; import { createUploadAtom, UploadSuccess } from '../../state/upload'; import { CompactUploadCardRenderer } from '../upload-card'; @@ -111,7 +111,7 @@ export function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfil return undefined; }, [imageFile]); - const pickFile = useFilePicker(setImageFile, false); + const pickFile = useStrippedFilePicker(setImageFile, false); const handleRemoveUpload = useCallback(() => { setImageFile(undefined); diff --git a/src/app/features/common-settings/permissions/PowersEditor.tsx b/src/app/features/common-settings/permissions/PowersEditor.tsx index 88d2655ff..76a424cf5 100644 --- a/src/app/features/common-settings/permissions/PowersEditor.tsx +++ b/src/app/features/common-settings/permissions/PowersEditor.tsx @@ -40,7 +40,7 @@ import { useImagePackRooms } from '../../../hooks/useImagePackRooms'; import { roomToParentsAtom } from '../../../state/room/roomToParents'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; -import { useFilePicker } from '../../../hooks/useFilePicker'; +import { useStrippedFilePicker } from '../../../hooks/useStrippedFilePicker'; import { CompactUploadCardRenderer } from '../../../components/upload-card'; import { createUploadAtom, UploadSuccess } from '../../../state/upload'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; @@ -68,7 +68,7 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) { const imagePackRooms = useImagePackRooms(room.roomId, roomToParents); const [iconFile, setIconFile] = useState(); - const pickFile = useFilePicker(setIconFile, false); + const pickFile = useStrippedFilePicker(setIconFile, false); const [tagColor, setTagColor] = useState(tag?.color); const [tagIcon, setTagIcon] = useState(tag?.icon); diff --git a/src/app/hooks/useStrippedFilePicker.ts b/src/app/hooks/useStrippedFilePicker.ts new file mode 100644 index 000000000..1b841b05b --- /dev/null +++ b/src/app/hooks/useStrippedFilePicker.ts @@ -0,0 +1,36 @@ +import { useCallback } from 'react'; +import { useFilePicker } from './useFilePicker'; +import { useSetting } from '../state/hooks/settings'; +import { settingsAtom } from '../state/settings'; +import { stripImageMetadata } from '../utils/stripImageMetadata'; + +/** + * [Gitea #332] `useFilePicker` for images that get published (emoji / sticker + * packs, pack avatars, power-level badges): applies "Remove Photo Metadata" + * like message attachments and avatars, so a phone photo's GPS location and + * timestamp don't go to every member of the room or space. + */ +export const useStrippedFilePicker = ( + onSelect: (file: M extends true ? File[] : File) => void, + multiple?: M, +) => { + const [strip] = useSetting(settingsAtom, 'stripImageMetadata'); + const handle = useCallback( + (picked: File | File[]) => { + if (!strip) { + onSelect(picked as M extends true ? File[] : File); + return; + } + const clean = (f: File) => stripImageMetadata(f).then((r) => r.file); + if (Array.isArray(picked)) { + Promise.all(picked.map(clean)).then((files) => + onSelect(files as M extends true ? File[] : File), + ); + } else { + clean(picked).then((file) => onSelect(file as M extends true ? File[] : File)); + } + }, + [strip, onSelect], + ); + return useFilePicker(handle as (file: M extends true ? File[] : File) => void, multiple); +};