Skip to content

fix(blob): strip JPEG metadata at the upload choke point - #1549

Merged
bmc08gt merged 2 commits into
code/cashfrom
fix/blob-upload-strip-metadata
Sep 24, 2026
Merged

bmc08gt merged 2 commits into
code/cashfrom
fix/blob-upload-strip-metadata

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

BlobStorageController.upload sent whatever bytes it was given. Today's uploads are clean only because the profile photo, create-group and edit-group screens all go through ImageUploadPreparer, and its Bitmap.compress re-encode writes no EXIF. An upload path that reads a picked file directly would send the photo's GPS coordinates with it.

iOS strips at its choke point: BlobUploader.store runs JPEGMetadata.stripped before reserving. This PR ports that walker to Kotlin and calls it in upload() the same way.

  • ByteArray.withoutJpegMetadata(): an internal extension in services/flipcash's internal/extensions, alongside ByteArray.toHash() and the other byte helpers, with no Android types. It keeps APP0 (JFIF), APP2 (ICC) and APP14 (Adobe) and drops every other APPn, EXIF and XMP included, plus COM segments. It handles fill bytes and the standalone TEM/RSTn markers, and stops at SOS. Input that isn't a JPEG, or whose segment lengths overrun the data, passes through untouched.
  • BlobStorageController.upload: strips before initiateExternalUpload. The reservation declares a byte count, so the reserved size has to match the size of the bytes actually sent.
  • Tests: all 8 iOS JPEGMetadataTests cases in JpegMetadataTest, ported with the same fixtures, plus a BlobStorageControllerTest case asserting that a JPEG carrying EXIF is uploaded stripped and that the reserved size matches the stripped length.

ImageUploadPreparer is unchanged; its re-encode still does the resizing.

BlobStorageController.upload sent whatever bytes it was given. Uploads
were clean only because every current caller goes through
ImageUploadPreparer, whose Bitmap.compress re-encode writes no EXIF. A
caller that uploads a picked file directly would send its GPS
coordinates along with it.

Port iOS's JPEGMetadata walker and run it in upload() before
initiateExternalUpload, so the reserved size is the size of the bytes
actually sent. It keeps APP0 (JFIF), APP2 (ICC) and APP14 (Adobe) and
drops every other APPn, EXIF and XMP included, plus COM segments. Input
that isn't a JPEG, or whose segment lengths overrun the data, passes
through untouched.

The iOS JPEGMetadataTests cases are ported one for one with the same
fixtures, so the two walkers are checked against the same inputs.
@bmc08gt bmc08gt self-assigned this Sep 24, 2026
@github-actions github-actions Bot added area: network gRPC, connectivity, API, exchange rates type: fix Bug fix labels Sep 24, 2026
…tension

The byte helpers in services/flipcash are top-level internal
extensions (ByteArray.toHash(), ByteArray.toPublicKey()), not
objects. Replace the public JPEGMetadata object with an internal
ByteArray.withoutJpegMetadata() in internal/extensions, next to them.
The walker's behaviour is unchanged.
@bmc08gt
bmc08gt merged commit b9e33f2 into code/cash Sep 24, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: network gRPC, connectivity, API, exchange rates type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant