mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
Stalwart 1.0 returns every photo, logo and sound in a contact card as a blobId without a uri, including every photo Bulwark saved as a data: URI. Bulwark read only `uri`, so avatars, the contact list and detail, print, vCard export and the "has photo" filter showed nothing, and the form sent `media` without the photo on every save, so editing a phone number deleted the photo on the server. - `ContactMedia` carries `blobId`, `label`, `pref` and `contexts`; `uri` is optional, and `getContactPhotoRef`/`hasContactPhoto` read both. - Blob photos are downloaded once each, at most two at a time and at a paced rate, only while on screen, into an LRU of object URLs revoked on eviction, account switch and logout. They share Stalwart's per-account budget of 4 concurrent requests and 1000 a minute. - Avatars look senders up in an index built once per contact list. - The form leaves `media` out unless the photo changed, and otherwise passes the other entries through as the server returned them. - A new photo is uploaded into the user's own account and sent as a blobId where the blob capability lists ContactCard (1.0). 0.16 refuses blobIds in media and keeps getting a data: URI; a refusal at runtime falls back the same way for the rest of the session. - Print and vCard export download blob media first; duplicate and cross-account move send the bytes instead of the source's blobIds. - vCard export also writes photos whose data: URI names no media type (`data:;base64,`, `data:base64,`), which it dropped before.
32 lines
1.5 KiB
TypeScript
32 lines
1.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { blobToDataUri, dataUriToBlob, parseBase64DataUri } from '../data-uri';
|
|
|
|
describe('parseBase64DataUri', () => {
|
|
it('reads the three shapes Stalwart writes', () => {
|
|
expect(parseBase64DataUri('data:image/jpeg;base64,/9j/')).toEqual({ type: 'image/jpeg', data: '/9j/' });
|
|
expect(parseBase64DataUri('data:;base64,/9j/')).toEqual({ type: undefined, data: '/9j/' });
|
|
expect(parseBase64DataUri('data:base64,/9j/')).toEqual({ type: undefined, data: '/9j/' });
|
|
expect(parseBase64DataUri('data:image/png;charset=x;base64,iVBO')).toEqual({ type: 'image/png', data: 'iVBO' });
|
|
});
|
|
|
|
it('refuses what is not a base64 data: URI', () => {
|
|
expect(parseBase64DataUri('https://x.test/p.jpg')).toBeNull();
|
|
expect(parseBase64DataUri('data:image/svg+xml,<svg/>')).toBeNull();
|
|
expect(parseBase64DataUri('data:image/jpeg;base64')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('dataUriToBlob / blobToDataUri', () => {
|
|
it('round-trips the bytes', async () => {
|
|
const blob = dataUriToBlob('data:;base64,/9j/4AAQ', 'image/jpeg')!;
|
|
expect(blob.type).toBe('image/jpeg');
|
|
expect(Array.from(new Uint8Array(await blob.arrayBuffer()))).toEqual([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10]);
|
|
expect(await blobToDataUri(blob)).toBe('data:image/jpeg;base64,/9j/4AAQ');
|
|
expect(await blobToDataUri(blob, 'image/png')).toBe('data:image/png;base64,/9j/4AAQ');
|
|
});
|
|
|
|
it('gives up on broken base64', () => {
|
|
expect(dataUriToBlob('data:image/jpeg;base64,***')).toBeNull();
|
|
});
|
|
});
|