[MOD] Basic work to get XMPP avatars through PEP

Attaboy!
This commit is contained in:
LDA 2024-09-19 23:24:46 +02:00
commit e54332e376
13 changed files with 428 additions and 25 deletions

View file

@ -4,6 +4,7 @@
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Uri.h>
#include <Cytoplasm/Sha.h>
#include <string.h>
#include <stdlib.h>
@ -127,3 +128,95 @@ ASReupload(const ParseeConfig *c, char *from, char **mime)
return ret;
}
bool
ASGetMIMESHA(const ParseeConfig *c, char *mxc, char **mime, char **sha)
{
HttpClientContext *cctx;
Stream *stream;
Stream *fake;
Uri *uri;
char *path, *buf = NULL;
unsigned char *sha1;
size_t len;
bool ret;
if (!c || !mxc || !mime || !sha)
{
return false;
}
*mime = NULL;
*sha = NULL;
if (!(uri = UriParse(mxc)) || !StrEquals(uri->proto, "mxc"))
{
return false;
}
path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path);
cctx = ParseeCreateRequest(c, HTTP_GET, path);
ASAuthenticateRequest(c, cctx);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
*mime = StrDuplicate(
HashMapGet(HttpResponseHeaders(cctx), "content-type")
);
stream = HttpClientStream(cctx);
fake = StreamFile(open_memstream(&buf, &len));
StreamCopy(stream, fake);
StreamClose(fake);
sha1 = Sha1Raw(buf, len);
free(buf);
*sha = ShaToHex(sha1, HASH_SHA1);
Free(sha1);
HttpClientContextFree(cctx);
UriFree(uri);
Free(path);
return true;
}
bool
ASGrab(const ParseeConfig *c, char *mxc, char **mime, char **out, size_t *len)
{
HttpClientContext *cctx;
Stream *stream;
Stream *fake;
Uri *uri;
char *path, *buf = NULL;
bool ret;
if (!c || !mxc || !mime || !out || !len)
{
return false;
}
*mime = NULL;
*out = NULL;
*len = 0;
if (!(uri = UriParse(mxc)) || !StrEquals(uri->proto, "mxc"))
{
return false;
}
path = StrConcat(3, "/_matrix/media/v3/download/", uri->host, uri->path);
cctx = ParseeCreateRequest(c, HTTP_GET, path);
ASAuthenticateRequest(c, cctx);
HttpRequestSendHeaders(cctx);
HttpRequestSend(cctx);
*mime = StrDuplicate(
HashMapGet(HttpResponseHeaders(cctx), "content-type")
);
stream = HttpClientStream(cctx);
fake = StreamFile(open_memstream(&buf, len));
StreamCopy(stream, fake);
StreamClose(fake);
*out = Malloc(*len);
memcpy(*out, buf, *len);
free(buf);
HttpClientContextFree(cctx);
UriFree(uri);
Free(path);
return true;
}

View file

@ -136,3 +136,72 @@ ASGetName(const ParseeConfig *c, char *room, char *user)
}
return ret;
}
char *
ASGetAvatar(const ParseeConfig *c, char *room, char *user)
{
HttpClientContext *ctx;
HashMap *reply;
char *path = NULL, *ret = NULL;
char *u2 = user;
if (!c || !user)
{
return NULL;
}
if (room)
{
user = HttpUrlEncode(user);
room = HttpUrlEncode(room);
path = StrConcat(4,
"/_matrix/client/v3/rooms/", room,
"/state/m.room.member/", user
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(user);
Free(room);
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "avatar_url"))
);
HttpClientContextFree(ctx);
JsonFree(reply);
Free(path);
user = u2;
Log(LOG_DEBUG, "ASGetAvatar: trying to grab avatar from room, got %s", ret);
}
if (!ret)
{
user = HttpUrlEncode(user);
path = StrConcat(3,
"/_matrix/client/v3/profile/", user, "/avatar_url"
);
ctx = ParseeCreateRequest(c, HTTP_GET, path);
Free(user);
user = u2;
ASAuthenticateRequest(c, ctx);
HttpRequestSendHeaders(ctx);
HttpRequestSend(ctx);
reply = JsonDecode(HttpClientStream(ctx));
ret = StrDuplicate(
JsonValueAsString(HashMapGet(reply, "avatar_url"))
);
StreamFlush(StreamStderr());
HttpClientContextFree(ctx);
JsonFree(reply);
Free(path);
Log(LOG_DEBUG, "ASGetAvatar: trying to grab avatar from profile, got %s", ret);
}
return ret;
}

View file

@ -78,6 +78,8 @@ Main(Array *args, HashMap *env)
);
ParseePrintASCII();
Log(LOG_INFO, "=======================");
Log(LOG_INFO, "(C)opyright 2023 LDA");
Log(LOG_INFO, "(This program is free software, see LICENSE.)");
LogConfigIndent(LogConfigGlobal());
{

View file

@ -16,7 +16,7 @@
static const char *
GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to);
static void
static char *
JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name)
{
char *sender = GrabString(event, 1, "sender");
@ -50,7 +50,7 @@ JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name)
ParseePushNickTable(muc, sender, nick);
Free(nick);
Free(rev);
return (rev);
}
static void
@ -98,13 +98,71 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
{
char *muc = ParseeGetMUCID(data, chat_id);
char *name = ASGetName(data->config, room_id, state_key);
char *avatar = ASGetAvatar(data->config, room_id, state_key);
char *jabber = JoinMUC(data, event, jid, muc, name);
JoinMUC(data, event, jid, muc, name);
Log(LOG_DEBUG, "MATRIX: Joining as '%s' (avatar=%s)", jabber, avatar);
Free(avatar);
Free(jabber);
Free(name);
Free(muc);
/* TODO: XEP-0084 magic to advertise a new avatar if possible. */
}
else
{
char *avatar = ASGetAvatar(data->config, room_id, state_key);
char *sha = NULL, *mime = NULL, *url = NULL;
char *full_jid = StrConcat(3,
jid, "@", data->config->component_host
);
XMLElement *elem, *pevent, *items, *item, *meta, *info;
Log(LOG_DEBUG, "MATRIX: Got local user '%s'(mxid=%s avatar=%s)", jid, state_key, avatar);
url = ParseeToUnauth(data, avatar);
elem = XMLCreateTag("message");
ASGetMIMESHA(data->config, avatar, &mime, &sha);
{
#define PUBSUB "http://jabber.org/protocol/pubsub"
#define AVATAR "urn:xmpp:avatar:metadata"
pevent = XMLCreateTag("event");
XMLAddAttr(pevent, "xmlns", PUBSUB "#event");
{
items = XMLCreateTag("items");
item = XMLCreateTag("item");
XMLAddAttr(items, "node", AVATAR);
XMLAddAttr(item, "id", sha);
{
meta = XMLCreateTag("metadata");
info = XMLCreateTag("info");
XMLAddAttr(meta, "xmlns", AVATAR);
XMLAddAttr(info, "id", sha);
XMLAddAttr(info, "url", url);
XMLAddAttr(info, "type", mime);
XMLAddChild(meta, info);
XMLAddChild(item, meta);
}
XMLAddChild(items, item);
XMLAddChild(pevent, items);
}
XMLAddChild(elem, pevent);
#undef PUBSUB
}
/* TODO: Broadcast PEP avatar change */
ParseeBroadcastStanza(data, full_jid, elem);
XMLFreeElement(elem);
Free(full_jid);
Free(avatar);
Free(mime);
Free(sha);
Free(url);
}
Free(jid);
Free(chat_id);
}
@ -263,7 +321,7 @@ GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
}
matrix_name = ASGetName(data->config, room_id, matrix_sender);
JoinMUC(data, event, *from, muc_id, matrix_name);
Free(JoinMUC(data, event, *from, muc_id, matrix_name));
*to = muc_id;
Free(matrix_name);
@ -350,12 +408,8 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
goto end;
}
/* TODO: Check the name's validity.
* Is there a good way to check for that that isn't
* just "await on join and try again?" */
name = ASGetName(data->config, id, m_sender);
JoinMUC(data, event, encoded_from, muc_id, name);
Free(JoinMUC(data, event, encoded_from, muc_id, name));
to = muc_id;

View file

@ -40,7 +40,7 @@ static bool
IsPubsubRequest(XMLElement *stanza)
{
char *type = HashMapGet(stanza ? stanza->attrs : NULL, "type");
XMLElement *pubsub, *subscribe;
XMLElement *pubsub;
if (!stanza)
{
return false;
@ -58,7 +58,6 @@ IsPubsubRequest(XMLElement *stanza)
return false;
}
Log(LOG_INFO, "WOAH");
return XMLookForUnique(pubsub, "subscribe");
}

View file

@ -1,5 +1,14 @@
#include "XMPPThread/internal.h"
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Base64.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Db.h>
#include <string.h>
static char *
SubscriptionHash(ParseeData *data, char *from, char *to)
{
@ -10,14 +19,28 @@ SubscriptionHash(ParseeData *data, char *from, char *to)
len = strlen(from) + 1 + strlen(to);
sum = Malloc(len);
memset(sum, 0x00, len);
memcpy(sum[0], from, strlen(from)):
memcpy(sum[strlen(from) + 1], to, strlen(to));
memcpy(&sum[0], from, strlen(from));
memcpy(&sum[strlen(from) + 1], to, strlen(to));
hash = ParseeHMAC(data->id, sum, len);
hash = Base64Encode(sum, len);
Free(sum);
return hash;
}
static void
DecodeSubscription(ParseeData *data, char *hash, char **from, char **to)
{
char *sum;
if (!data || !hash || !from || !to)
{
return;
}
sum = Base64Decode(hash, strlen(hash));
*from = StrDuplicate(sum);
*to = StrDuplicate(sum + strlen(sum) + 1);
Free(sum);
}
void
AddPresenceSubscriber(ParseeData *data, char *from, char *to)
@ -32,13 +55,18 @@ AddPresenceSubscriber(ParseeData *data, char *from, char *to)
database = data->db;
hash = SubscriptionHash(data, from, to);
ref = DbCreate(database, 2, "subscriptions", hash);
ref = DbCreate(database, 2, "subs", hash);
if (!ref)
{
goto end;
}
HashMapSet(DbRef(ref), "from", JsonValueString(from));
HashMapSet(DbRef(ref), "to", JsonValueString(to));
HashMapSet(DbJson(ref), "from", JsonValueString(from));
HashMapSet(DbJson(ref), "to", JsonValueString(to));
/* I don't think we need more information right now */
DbClose(database, ref);
end:
DbUnlock(database, ref);
Free(hash);
}
@ -48,15 +76,73 @@ IsSubscribed(ParseeData *data, char *user, char *to)
Db *database;
char *hash;
bool ret;
if (!data || !from || !to)
if (!data || !user || !to)
{
return;
return false;
}
database = data->db;
hash = SubscriptionHash(data, from, to);
ret = DbExists(database, 2, "subscriptions", hash);
hash = SubscriptionHash(data, user, to);
ret = DbExists(database, 2, "subs", hash);
Free(hash);
return ret;
}
void
ParseeBroadcastStanza(ParseeData *data, char *from, XMLElement *stanza)
{
XMPPComponent *jabber = data ? data->jabber : NULL;
Array *entries;
size_t i;
if (!data || !from || !stanza)
{
return;
}
/* Copy our stanza so that we can freely modify it */
stanza = XMLCopy(stanza);
/* Start doing a storm on Mt. Subs. */
entries = DbList(data->db, 1, "subs");
for (i = 0; i < ArraySize(entries); i++)
{
char *entry = ArrayGet(entries, i);
char *entry_from = NULL, *entry_to = NULL;
char *storm_id; /* ooe */
XMLElement *sub;
DecodeSubscription(data, entry, &entry_from, &entry_to);
if (!StrEquals(entry_to, from))
{
goto end;
}
Log(LOG_DEBUG,
"PRESENCE SYSTEM: "
"We should be brotkasting straight to %s (from %s)",
entry_from, from
);
sub = XMLCopy(stanza);
XMLAddAttr(sub, "from", from);
XMLAddAttr(sub, "to", entry_from);
/* TODO: Should we store IDs somewhere? */
XMLAddAttr(sub, "id", (storm_id = StrRandom(16)));
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, sub);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(sub);
Free(storm_id);
end:
Free(entry_from);
Free(entry_to);
}
DbListFree(entries);
XMLFreeElement(stanza);
}

View file

@ -327,6 +327,7 @@ void
IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{
XMPPComponent *jabber = args->jabber;
XMLElement *pubsub;
char *from = HashMapGet(stanza->attrs, "from");
char *to = HashMapGet(stanza->attrs, "to");
char *id = HashMapGet(stanza->attrs, "id");
@ -395,6 +396,68 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
XMLFreeElement(iqVCard);
}
}
#define PS "http://jabber.org/protocol/pubsub"
else if ((pubsub = XMLookForTKV(stanza, "pubsub", "xmlns", PS)))
{
/* TODO: Pass this through the PEP manager */
XMLElement *a_items = XMLookForTKV(pubsub,
"items", "node", "urn:xmpp:avatar:data"
);
if (a_items)
{
/* Do, without regret, start shoving an avatar out the bus */
char *to_matrix = ParseeDecodeMXID(to);
char *avatar = ASGetAvatar(args->config, NULL, to_matrix);
char *buf, *mime;
char *b64;
size_t len;
XMLElement *reply;
ASGrab(args->config, avatar, &mime, &buf, &len);
b64 = Base64Encode(buf, len);
Free(buf);
Log(LOG_INFO, "FM=%s", to_matrix);
Log(LOG_INFO, "B=%s (%dB)", b64, (int) len);
/* Strike back with a response */
reply = XMLCreateTag("iq");
XMLAddAttr(reply, "type", "result");
XMLAddAttr(reply, "to", from);
XMLAddAttr(reply, "from", to);
XMLAddAttr(reply, "id", HashMapGet(stanza->attrs, "id"));
{
XMLElement *ps = XMLCreateTag("pubsub");
XMLElement *items = XMLCreateTag("items");
XMLAddAttr(ps, "xmlns", PS);
XMLAddAttr(items, "node", "urn:xmpp:avatar:data");
{
XMLElement *item = XMLCreateTag("item");
XMLElement *data = XMLCreateTag("data");
XMLAddAttr(item, "id", "TODO");
XMLAddAttr(data, "xmlns", "urn:xmpp:avatar:data");
XMLAddChild(data, XMLCreateText(b64));
XMLAddChild(item, data);
XMLAddChild(items, item);
}
XMLAddChild(ps, items);
XMLAddChild(reply, ps);
}
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, reply);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(reply);
Free(to_matrix);
Free(avatar);
Free(mime);
Free(b64);
}
}
#undef PS
else if (XMLookForTKV(stanza, "query", "xmlns", DISCO))
{
IQDiscoGet(args, jabber, stanza);

View file

@ -97,6 +97,17 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
Log(LOG_DEBUG, "<message/> usage=%d (%s:%d)", MemoryAllocated(), __FILE__, __LINE__);
return false;
}
/*{
XMLElement *foo = XMLCreateTag("message");
XMLElement *body = XMLCreateTag("body");
XMLAddAttr(foo, "type", "chat");
XMLAddChild(foo, body);
XMLAddChild(body, XMLCreateText("Storm on Mt. Ooe (sorry if you see this)"));
BroadcastStanza(args, HashMapGet(stanza->attrs, "to"), foo);
XMLFreeElement(foo);
}*/
if (ServerHasXEP421(args, from))
{

View file

@ -141,6 +141,14 @@ extern void ASSetStatus(const ParseeConfig *c, char *user, UserStatus status, ch
* Modifies: NOTHING */
extern char * ASGetName(const ParseeConfig *c, char *room, char *user);
/** Returns the user's avatar in a room, or a the global user avatar, to be
* Free'd
* -------------
* Returns: The user's name in the [HEAP] | NULL
* Thrasher: Free
* Modifies: NOTHING */
extern char * ASGetAvatar(const ParseeConfig *c, char *room, char *user);
/** Uploads data to Matrix to be used later
* ----------------
* Returns: A valid MXC URI[HEAP] | NULL
@ -170,4 +178,16 @@ extern Array * ASGetRelations(const ParseeConfig *c, size_t n, char *room, char
* Thrashes: {relations}
* See-Also: ASGetRelations */
extern void ASFreeRelations(Array *relations);
/** Returns the MIME and SHA-1 hash of a media entry, in one fell swoop.
* -----------------
* Returns: whenever the media exists
* Modifies: {mime}[HEAP], {sha}[HEAP] */
extern bool ASGetMIMESHA(const ParseeConfig *c, char *mxc, char **mime, char **sha);
/** Retrieves media off an MXC link.
* ------------
* Returns: whenever the media exists
* Modifies {mime}[HEAP], {out}[HEAP], {len} */
extern bool ASGrab(const ParseeConfig *c, char *mxc, char **mime, char **out, size_t *len);
#endif

View file

@ -401,4 +401,10 @@ extern void ParseeSetThreads(int xmpp, int http);
extern char * ParseeHMAC(char *key, uint8_t *msg, size_t msglen);
#define ParseeHMACS(key, msg) ParseeHMAC(key, (uint8_t *) msg, strlen(msg))
/** Broadcasts a stanza from a user to any that may be interested by it
* (PEP or subscription)
* -------------------------------------
* Returns: NOTHING */
extern void ParseeBroadcastStanza(ParseeData *data, char *from, XMLElement *s);
#endif