Files
cc-switch/tests/fixtures
Jason 21203803ed feat(providers): upload a custom provider icon
The icon picker gets an "Upload Image" button (PNG, JPG, WebP or SVG, up
to 2 MB and 4096 px). The image is redrawn on a canvas into a 96x96 PNG
and stored as a data URI in the existing `icon` column, so it travels
with the row through cloud sync, duplication and profiles. No new column,
no files on disk, nothing keyed by provider id (ids are shared across
apps). Opaque square images fill the icon box; anything else is padded to
the size of a built-in transparent logo. Uploading only changes the form;
nothing is written until Save.

The original file never reaches the database: SVG is loaded through
<img> (no scripts, no external requests) and rasterized, and re-encoding
drops EXIF. Custom icons are only ever rendered with <img>; remote URLs
are never loaded as icons.

Validation at every way in:
- Saving a provider or a universal provider, and syncing a universal
  provider to its apps, fully decodes the PNG with the `png` crate
  (already in the tree via `image`) under a 1 MiB decode limit and caps
  it at 64 KiB and 256x256. Other strings still pass as icon names so old
  rows stay editable.
- SQL import and cloud sync bypass saving, so the staging database drops
  invalid custom icons from both `providers` and the universal provider
  JSON before it replaces the main one. The rest of the backup imports
  normally.
- Rendering checks the length and reads the PNG signature, IHDR length
  and dimensions from the header before showing an image, and falls back
  to initials if it fails to decode. Browsers sniff image formats, so
  without the signature check a disguised GIF could slip past the size
  limit.

Also:
- Uploads still being processed are dropped once the user removes the
  icon, picks a built-in one, uploads again or closes the picker.
- Aggregation mode no longer derives model key prefixes from a custom
  icon; it falls back to the provider name.
- The deep-link `icon` parameter only accepts built-in icon names; other
  values are dropped instead of failing the link, so links cannot put a
  data URI straight into the database.

Refs #8117
2026-10-12 23:26:43 +08:00
..