| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-07-21 15:47:59 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-07-21 15:47:59 UTC |
| parent | d32dd886f6d413b3a3d302a3f6b5be4d505e122f |
| borogove/ChatMessage.hx | +53 | -20 |
| borogove/Client.hx | +16 | -15 |
| borogove/Stanza.hx | +2 | -1 |
diff --git a/borogove/ChatMessage.hx b/borogove/ChatMessage.hx index 81c40a2..3418802 100644 --- a/borogove/ChatMessage.hx +++ b/borogove/ChatMessage.hx @@ -80,7 +80,7 @@ class ChatAttachment { /** Hashes of data **/ - public final hashes: ReadOnlyArray<Hash>; + public final hashes: Array<Hash>; #if cpp @:allow(borogove) @@ -130,6 +130,30 @@ class ChatAttachment { return Promise.resolve(id); }); } + + @:allow(borogove) + private function sims() { + final stanza = new Stanza("reference", { xmlns: "urn:xmpp:reference:0", type: "data" }) + .tag("media-sharing", { xmlns: "urn:xmpp:sims:1" }); + + stanza.tag("file", { xmlns: "urn:xmpp:jingle:apps:file-transfer:5" }); + if (name != null) stanza.textTag("name", name); + stanza.textTag("media-type", mime); + if (size != null) stanza.textTag("size", Std.string(size)); + for (hash in hashes) { + stanza.textTag("hash", Base64.encode(Bytes.ofData(hash.hash)), { xmlns: "urn:xmpp:hashes:2", algo: hash.algorithm }); + } + stanza.up(); + + stanza.tag("sources"); + for (uri in uris) { + stanza.tag("reference", { xmlns: "urn:xmpp:reference:0", type: "data", uri: uri }).up(); + } + + stanza.up().up().up(); + + return stanza; + } } @:expose @@ -547,6 +571,33 @@ class ChatMessage { } } + /** + Fetch data for a ChatAttachment into local media cache + + If cachedAt is already filled in for this ChatAttachment, it is simply returned. + + @param attachment ChatAttachment to fetch + @param client Client to use when fetching + @returns Promise resolving to a ChatAttachment with cachedAt filled in, if possible + **/ + public function fetchAttachment(attachment: ChatAttachment, client: Client) { + final hasNoHashes = attachment.hashes.length < 1; + return client.fetchAttachment(attachment).then(r -> { + if (hasNoHashes && r.hashes.length > 0) { + if (stanza != null) { + final sims = stanza.allTags().find(child -> switch child.find("{urn:xmpp:sims:1}media-sharing/sources/{urn:xmpp:reference:0}reference@uri") { + case CData(txt): txt.content == attachment.uris[0]; + default: false; + }); + if (sims != null && stanza != null) stanza.removeChild(sims); + if (stanza != null) stanza.addChild(r.sims()); + } + return client.storeMessages([this]).then(_ -> r); + } + return Promise.resolve(r); + }); + } + @:allow(borogove) private function asStanza():Stanza { if (stanza != null) return stanza; @@ -613,25 +664,7 @@ class ChatMessage { } for (attachment in attachments) { - stanza - .tag("reference", { xmlns: "urn:xmpp:reference:0", type: "data" }) - .tag("media-sharing", { xmlns: "urn:xmpp:sims:1" }); - - stanza.tag("file", { xmlns: "urn:xmpp:jingle:apps:file-transfer:5" }); - if (attachment.name != null) stanza.textTag("name", attachment.name); - stanza.textTag("media-type", attachment.mime); - if (attachment.size != null) stanza.textTag("size", Std.string(attachment.size)); - for (hash in attachment.hashes) { - stanza.textTag("hash", Base64.encode(Bytes.ofData(hash.hash)), { xmlns: "urn:xmpp:hashes:2", algo: hash.algorithm }); - } - stanza.up(); - - stanza.tag("sources"); - for (uri in attachment.uris) { - stanza.tag("reference", { xmlns: "urn:xmpp:reference:0", type: "data", uri: uri }).up(); - } - - stanza.up().up().up(); + stanza.addChild(attachment.sims()); if (attachment.uris.length > 0) { stanza.tag("x", { xmlns: "jabber:x:oob" }).textTag("url", attachment.uris[0]).up(); diff --git a/borogove/Client.hx b/borogove/Client.hx index 80f8811..82d9393 100644 --- a/borogove/Client.hx +++ b/borogove/Client.hx @@ -1108,32 +1108,26 @@ class Client extends EventEmitter { return EventHandled; } - /** - Fetch data for a ChatAttachment into local media cache - - If cachedAt is already filled in for this ChatAttachment, it is simply returned. - - @param attachment ChatAttachment to fetch - @returns Promise resolving to a ChatAttachment with cachedAt filled in, if possible - **/ - public function fetchAttachment(attachment: ChatAttachment): Promise<ChatAttachment> { + @:allow(borogove) + private function fetchAttachment(attachment: ChatAttachment): Promise<ChatAttachment> { // We already have it if (attachment.cachedAt != null) return Promise.resolve(attachment); - return fetchUris( attachment.uris.copy()).then(id -> { - attachment.cachedAt = id; + return fetchUris( attachment.uris.copy()).then(r -> { + attachment.cachedAt = r.id; + if (attachment.hashes.length < 1) attachment.hashes.push(r.hash); return attachment; }); } - private function fetchUris(uris: Array<String>): Promise<Null<String>> { + private function fetchUris(uris: Array<String>): Promise<Null<{ id: String, hash: Hash }>> { if (uris.length < 1) return Promise.resolve(null); final uri = uris.shift(); final aesgcm = XEP0454.parse(uri); if (aesgcm != null) { return XEP0454.fetch(aesgcm).then( - data -> persistence.storeMedia(aesgcm.mime, data), + data -> persistence.storeMedia(aesgcm.mime, data).then(id -> { id: id, hash: Hash.sha256(data) }), e -> { trace("fetchAttachment", e); return fetchUris(uris); @@ -1142,6 +1136,7 @@ class Client extends EventEmitter { } if (uri.startsWith("http://") || uri.startsWith("https://")) { + final sha256 = Hash.sha256incr(); return new Promise((resolve, reject) -> { tink.http.Client.fetch(uri).handle((rOrErr) -> switch (rOrErr) { case Success(r): @@ -1152,13 +1147,19 @@ class Client extends EventEmitter { case Success(ct): ct.toString(); default: "application/octet-stream"; }; - resolve({ mime: mime, body: r.body }); + resolve({ + mime: mime, + body: Source.ofTinkSource(r.body.chunked().map(chunk -> { + sha256.update((chunk : Bytes).getData()); + return chunk; + })) + }); } case Failure(e): reject(e); }); }).then( - r -> persistence.storeMedia(r.mime, r.body), + r -> persistence.storeMedia(r.mime, r.body).then(id -> { id: id, hash: sha256.digest() }), e -> fetchUris(uris) ); } diff --git a/borogove/Stanza.hx b/borogove/Stanza.hx index 280a49d..8d42b7e 100644 --- a/borogove/Stanza.hx +++ b/borogove/Stanza.hx @@ -347,7 +347,8 @@ class Stanza { } return CData(new TextNode(text)); } - return Element(cursor.getChild(name, xmlns)); + final got = cursor.getChild(name, xmlns); + return got == null ? null : Element(got); } cursor = cursor.getChild(name, xmlns); } while (cursor != null);