diff options
Diffstat (limited to 'src/xmpp')
-rw-r--r-- | src/xmpp/stanza.c | 19 | ||||
-rw-r--r-- | src/xmpp/stanza.h | 3 | ||||
-rw-r--r-- | src/xmpp/vcard.c | 1613 | ||||
-rw-r--r-- | src/xmpp/vcard.h | 171 | ||||
-rw-r--r-- | src/xmpp/vcard_funcs.h | 66 |
5 files changed, 1872 insertions, 0 deletions
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c index 6b8377b6..fc2fcdc6 100644 --- a/src/xmpp/stanza.c +++ b/src/xmpp/stanza.c @@ -2688,6 +2688,25 @@ stanza_create_avatar_metadata_publish_iq(xmpp_ctx_t* ctx, const char* img_data, } xmpp_stanza_t* +stanza_create_vcard_request_iq(xmpp_ctx_t* ctx, const char* const jid, const char* const stanza_id) +{ + xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_GET, stanza_id); + xmpp_stanza_set_from(iq, connection_get_fulljid()); + if (jid) { + xmpp_stanza_set_to(iq, jid); + } + + xmpp_stanza_t* vcard = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(vcard, STANZA_NAME_VCARD); + xmpp_stanza_set_ns(vcard, STANZA_NS_VCARD); + + xmpp_stanza_add_child(iq, vcard); + xmpp_stanza_release(vcard); + + return iq; +} + +xmpp_stanza_t* stanza_attach_correction(xmpp_ctx_t* ctx, xmpp_stanza_t* stanza, const char* const replace_id) { xmpp_stanza_t* replace_stanza = xmpp_stanza_new(ctx); diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h index 12c9a5ee..ddd46e9a 100644 --- a/src/xmpp/stanza.h +++ b/src/xmpp/stanza.h @@ -123,6 +123,7 @@ #define STANZA_NAME_MOOD "mood" #define STANZA_NAME_RECEIVED "received" #define STANZA_NAME_SENT "sent" +#define STANZA_NAME_VCARD "vCard" // error conditions #define STANZA_NAME_BAD_REQUEST "bad-request" @@ -249,6 +250,7 @@ #define STANZA_NS_MOOD_NOTIFY "http://jabber.org/protocol/mood+notify" #define STANZA_NS_STREAMS "http://etherx.jabber.org/streams" #define STANZA_NS_XMPP_STREAMS "urn:ietf:params:xml:ns:xmpp-streams" +#define STANZA_NS_VCARD "vcard-temp" #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo" @@ -413,6 +415,7 @@ void stanza_free_caps(XMPPCaps* caps); xmpp_stanza_t* stanza_create_avatar_retrieve_data_request(xmpp_ctx_t* ctx, const char* stanza_id, const char* const item_id, const char* const jid); xmpp_stanza_t* stanza_create_avatar_data_publish_iq(xmpp_ctx_t* ctx, const char* img_data, gsize len); xmpp_stanza_t* stanza_create_avatar_metadata_publish_iq(xmpp_ctx_t* ctx, const char* img_data, gsize len, int height, int width); +xmpp_stanza_t* stanza_create_vcard_request_iq(xmpp_ctx_t* ctx, const char* const jid, const char* const stanza_id); xmpp_stanza_t* stanza_create_mam_iq(xmpp_ctx_t* ctx, const char* const jid, const char* const startdate, const char* const lastid); xmpp_stanza_t* stanza_change_password(xmpp_ctx_t* ctx, const char* const user, const char* const password); xmpp_stanza_t* stanza_register_new_account(xmpp_ctx_t* ctx, const char* const user, const char* const password); diff --git a/src/xmpp/vcard.c b/src/xmpp/vcard.c new file mode 100644 index 00000000..6d2f0b1e --- /dev/null +++ b/src/xmpp/vcard.c @@ -0,0 +1,1613 @@ +/* + * vcard.c + * vim: expandtab:ts=4:sts=4:sw=4 + * + * Copyright (C) 2022 Marouane L. <techmetx11@disroot.org> + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Profanity. If not, see <https://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#include <assert.h> +#include <errno.h> +#include <glib.h> +#include <strophe.h> +#include <sys/stat.h> + +#include "xmpp/vcard.h" +#include "config/files.h" +#include "config/preferences.h" +#include "ui/ui.h" +#include "ui/window_list.h" +#include "xmpp/connection.h" +#include "xmpp/iq.h" +#include "xmpp/stanza.h" + +// Connected account's vCard +vCard* vcard_user = NULL; + +typedef struct +{ + vCard* vcard; + ProfWin* window; + + // for photo + int photo_index; + gboolean open; + char* filename; +} _userdata; + +static void +_free_vcard_element(void* velement) +{ + vcard_element_t* element = (vcard_element_t*)velement; + + switch (element->type) { + case VCARD_NICKNAME: + { + if (element->nickname) { + free(element->nickname); + } + break; + } + case VCARD_PHOTO: + { + if (element->photo.external) { + if (element->photo.extval) { + free(element->photo.extval); + } + } else { + if (element->photo.data) { + g_free(element->photo.data); + } + if (element->photo.type) { + free(element->photo.type); + } + } + break; + } + case VCARD_BIRTHDAY: + { + g_date_time_unref(element->birthday); + break; + } + case VCARD_ADDRESS: + { + if (element->address.pobox) { + free(element->address.pobox); + } + if (element->address.extaddr) { + free(element->address.extaddr); + } + if (element->address.street) { + free(element->address.street); + } + if (element->address.locality) { + free(element->address.locality); + } + if (element->address.region) { + free(element->address.region); + } + if (element->address.pcode) { + free(element->address.pcode); + } + if (element->address.country) { + free(element->address.country); + } + break; + } + case VCARD_TELEPHONE: + { + if (element->telephone.number) { + free(element->telephone.number); + } + break; + } + case VCARD_EMAIL: + { + if (element->email.userid) { + free(element->email.userid); + } + break; + } + case VCARD_JID: + { + if (element->jid) { + free(element->jid); + } + break; + } + case VCARD_TITLE: + { + if (element->title) { + free(element->title); + } + break; + } + case VCARD_ROLE: + { + if (element->role) { + free(element->role); + } + break; + } + case VCARD_NOTE: + { + if (element->note) { + free(element->note); + } + break; + } + case VCARD_URL: + if (element->url) { + free(element->url); + } + break; + } + + free(element); +} + +void +vcard_free_full(vCard* vcard) +{ + if (!vcard) { + return; + } + + // Free the fullname element + if (vcard->fullname) { + free(vcard->fullname); + } + + // Free the name element + if (vcard->name.family) { + free(vcard->name.family); + } + if (vcard->name.given) { + free(vcard->name.given); + } + if (vcard->name.middle) { + free(vcard->name.middle); + } + if (vcard->name.prefix) { + free(vcard->name.prefix); + } + if (vcard->name.suffix) { + free(vcard->name.suffix); + } + + // Clear the elements queue + g_queue_clear_full(vcard->elements, _free_vcard_element); +} + +vCard* +vcard_new(void) +{ + vCard* vcard = calloc(1, sizeof(vCard)); + + if (!vcard) { + return NULL; + } + + vcard->elements = g_queue_new(); + + return vcard; +} + +void +vcard_free(vCard* vcard) +{ + if (!vcard) { + return; + } + + g_queue_free(vcard->elements); + free(vcard); +} + +static void +_free_userdata(_userdata* data) +{ + vcard_free_full(data->vcard); + vcard_free(data->vcard); + + if (data->filename) { + free(data->filename); + } + + free(data); +} + +// Function must be called with <vCard> root element +gboolean +vcard_parse(xmpp_stanza_t* vcard_xml, vCard* vcard) +{ + // 2 bits + // 01 = fullname is set + // 10 = name is set + // Fullname and name can only be set once, if another + // fullname, or name element is found, then it will be + // ignored + char flags = 0; + + if (!vcard_xml) { + // vCard XML is null, stop + return FALSE; + } + + // Pointer to children of the vCard element + xmpp_stanza_t* child_pointer = xmpp_stanza_get_children(vcard_xml); + + // Connection context, for freeing elements allocated by Strophe + const xmpp_ctx_t* ctx = connection_get_ctx(); + + // Pointer to the children of the children + xmpp_stanza_t* child_pointer2 = NULL; + + for (; child_pointer != NULL; child_pointer = xmpp_stanza_get_next(child_pointer)) { + if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "FN") && !(flags & 1)) { + vcard->fullname = stanza_text_strdup(child_pointer); + flags |= 1; + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "N") && !(flags & 2)) { + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "FAMILY"); + vcard->name.family = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "GIVEN"); + vcard->name.given = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "MIDDLE"); + vcard->name.middle = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "PREFIX"); + vcard->name.prefix = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "SUFFIX"); + vcard->name.suffix = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + flags |= 2; + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "NICKNAME")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_NICKNAME; + element->nickname = stanza_text_strdup(child_pointer); + + if (!element->nickname) { + // Invaild element, free and do not push + free(element); + continue; + } + + g_queue_push_tail(vcard->elements, element); + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "PHOTO")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_PHOTO; + + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "EXTVAL"); + if (child_pointer2) { + element->photo.external = TRUE; + element->photo.extval = stanza_text_strdup(child_pointer2); + } else { + element->photo.external = FALSE; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "BINVAL"); + if (!child_pointer2) { + // No EXTVAL or BINVAL, invalid photo, skipping + free(element); + continue; + } + char* photo_base64 = xmpp_stanza_get_text(child_pointer2); + if (!photo_base64) { + free(element); + continue; + } + element->photo.data = g_base64_decode(photo_base64, &element->photo.length); + xmpp_free(ctx, photo_base64); + + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "TYPE"); + if (!child_pointer2) { + // No TYPE, invalid photo, skipping + if (element->photo.data) { + g_free(element->photo.data); + } + free(element); + continue; + } + + element->photo.type = stanza_text_strdup(child_pointer2); + } + g_queue_push_tail(vcard->elements, element); + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "BDAY")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_BIRTHDAY; + + char* bday_text = xmpp_stanza_get_text(child_pointer); + if (!bday_text) { + free(element); + continue; + } + + // Check if the birthday string is a date/time or date only string + char* check_pointer = bday_text; + gboolean is_datetime = FALSE; + for (; *check_pointer != 0; check_pointer++) { + if (*check_pointer == 'T' || *check_pointer == 't' || *check_pointer == ' ') { + is_datetime = TRUE; + break; + } + } + + if (!is_datetime) { + // glib doesn't parse ISO8601 date only strings, so we're gonna + // add a time string to make it parse + GString* date_string = g_string_new(bday_text); + g_string_append(date_string, "T00:00:00Z"); + + element->birthday = g_date_time_new_from_iso8601(date_string->str, NULL); + + g_string_free(date_string, TRUE); + } else { + element->birthday = g_date_time_new_from_iso8601(bday_text, NULL); + } + + xmpp_free(ctx, bday_text); + + if (!element->birthday) { + // Invalid birthday ISO 8601 date, skipping + free(element); + continue; + } + g_queue_push_tail(vcard->elements, element); + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "ADR")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_ADDRESS; + if (xmpp_stanza_get_child_by_name(child_pointer, "HOME")) { + element->address.options |= VCARD_HOME; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "WORK")) { + element->address.options |= VCARD_WORK; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "POSTAL")) { + element->address.options |= VCARD_POSTAL; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "PARCEL")) { + element->address.options |= VCARD_PARCEL; + } + + if (xmpp_stanza_get_child_by_name(child_pointer, "DOM")) { + element->address.options |= VCARD_DOM; + } else if (xmpp_stanza_get_child_by_name(child_pointer, "INTL")) { + element->address.options |= VCARD_INTL; + } + + if (xmpp_stanza_get_child_by_name(child_pointer, "PREF")) { + element->address.options |= VCARD_PREF; + } + + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "POBOX"); + element->address.pobox = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "EXTADD"); + element->address.extaddr = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "STREET"); + element->address.street = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "LOCALITY"); + element->address.locality = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "REGION"); + element->address.region = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "PCODE"); + element->address.pcode = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "CTRY"); + element->address.country = child_pointer2 ? stanza_text_strdup(child_pointer2) : NULL; + g_queue_push_tail(vcard->elements, element); + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "TEL")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_TELEPHONE; + if (xmpp_stanza_get_child_by_name(child_pointer, "HOME")) { + element->telephone.options |= VCARD_HOME; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "WORK")) { + element->telephone.options |= VCARD_WORK; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "VOICE")) { + element->telephone.options |= VCARD_TEL_VOICE; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "FAX")) { + element->telephone.options |= VCARD_TEL_FAX; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "PAGER")) { + element->telephone.options |= VCARD_TEL_PAGER; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "MSG")) { + element->telephone.options |= VCARD_TEL_MSG; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "CELL")) { + element->telephone.options |= VCARD_TEL_CELL; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "VIDEO")) { + element->telephone.options |= VCARD_TEL_VIDEO; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "BBS")) { + element->telephone.options |= VCARD_TEL_BBS; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "MODEM")) { + element->telephone.options |= VCARD_TEL_MODEM; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "ISDN")) { + element->telephone.options |= VCARD_TEL_ISDN; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "PCS")) { + element->telephone.options |= VCARD_TEL_PCS; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "PREF")) { + element->telephone.options |= VCARD_PREF; + } + + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "NUMBER"); + if (!child_pointer2) { + // No NUMBER, invalid telephone, skipping + free(element); + continue; + } + element->telephone.number = stanza_text_strdup(child_pointer2); + g_queue_push_tail(vcard->elements, element); + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "EMAIL")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_EMAIL; + if (xmpp_stanza_get_child_by_name(child_pointer, "HOME")) { + element->email.options |= VCARD_HOME; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "WORK")) { + element->email.options |= VCARD_WORK; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "INTERNET")) { + element->email.options |= VCARD_EMAIL_INTERNET; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "X400")) { + element->email.options |= VCARD_EMAIL_X400; + } + if (xmpp_stanza_get_child_by_name(child_pointer, "PREF")) { + element->email.options |= VCARD_PREF; + } + + child_pointer2 = xmpp_stanza_get_child_by_name(child_pointer, "USERID"); + if (!child_pointer2) { + // No USERID, invalid email, skipping + free(element); + continue; + } + element->email.userid = stanza_text_strdup(child_pointer2); + g_queue_push_tail(vcard->elements, element); + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "JABBERID")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_JID; + element->jid = stanza_text_strdup(child_pointer); + + if (!element->jid) { + // Invalid element, free and do not push + free(element); + continue; + } + + g_queue_push_tail(vcard->elements, element); + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "TITLE")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_TITLE; + element->title = stanza_text_strdup(child_pointer); + + if (!element->title) { + // Invalid element, free and do not push + free(element); + continue; + } + + g_queue_push_tail(vcard->elements, element); + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "ROLE")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_ROLE; + element->role = stanza_text_strdup(child_pointer); + + if (!element->role) { + // Invalid element, free and do not push + free(element); + continue; + } + + g_queue_push_tail(vcard->elements, element); + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "NOTE")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_NOTE; + element->note = stanza_text_strdup(child_pointer); + + if (!element->note) { + // Invalid element, free and do not push + free(element); + continue; + } + + g_queue_push_tail(vcard->elements, element); + } else if (!g_strcmp0(xmpp_stanza_get_name(child_pointer), "URL")) { + vcard_element_t* element = calloc(1, sizeof(vcard_element_t)); + if (!element) { + // Allocation failed + continue; + } + + element->type = VCARD_URL; + element->url = stanza_text_strdup(child_pointer); + + if (!element->note) { + // Invalid element, free and do not push + free(element); + continue; + } + + g_queue_push_tail(vcard->elements, element); + } + } + return TRUE; +} + +xmpp_stanza_t* +vcard_to_xml(xmpp_ctx_t* const ctx, vCard* vcard) +{ + if (!vcard || !ctx) { + return NULL; + } + + xmpp_stanza_t* vcard_stanza = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(vcard_stanza, STANZA_NAME_VCARD); + xmpp_stanza_set_ns(vcard_stanza, STANZA_NS_VCARD); + + if (vcard->fullname) { + // <FN> element + xmpp_stanza_t* fn = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(fn, "FN"); + + xmpp_stanza_t* fn_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(fn_text, vcard->fullname); + xmpp_stanza_add_child(fn, fn_text); + xmpp_stanza_release(fn_text); + + xmpp_stanza_add_child(vcard_stanza, fn); + xmpp_stanza_release(fn); + } + + if (vcard->name.family || vcard->name.given || vcard->name.middle || vcard->name.prefix || vcard->name.suffix) { + // <NAME> element + xmpp_stanza_t* name = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(name, "NAME"); + + if (vcard->name.family) { + xmpp_stanza_t* name_family = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(name_family, "FAMILY"); + + xmpp_stanza_t* name_family_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(name_family_text, vcard->name.family); + xmpp_stanza_add_child(name_family, name_family_text); + xmpp_stanza_release(name_family_text); + + xmpp_stanza_add_child(name, name_family); + xmpp_stanza_release(name_family); + } + + if (vcard->name.given) { + xmpp_stanza_t* name_given = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(name_given, "GIVEN"); + + xmpp_stanza_t* name_given_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(name_given_text, vcard->name.given); + xmpp_stanza_add_child(name_given, name_given_text); + xmpp_stanza_release(name_given_text); + + xmpp_stanza_add_child(name, name_given); + xmpp_stanza_release(name_given); + } + + if (vcard->name.middle) { + xmpp_stanza_t* name_middle = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(name_middle, "MIDDLE"); + + xmpp_stanza_t* name_middle_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(name_middle_text, vcard->name.middle); + xmpp_stanza_add_child(name_middle, name_middle_text); + xmpp_stanza_release(name_middle_text); + + xmpp_stanza_add_child(name, name_middle); + xmpp_stanza_release(name_middle); + } + + if (vcard->name.prefix) { + xmpp_stanza_t* name_prefix = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(name_prefix, "PREFIX"); + + xmpp_stanza_t* name_prefix_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(name_prefix_text, vcard->name.prefix); + xmpp_stanza_add_child(name_prefix, name_prefix_text); + xmpp_stanza_release(name_prefix_text); + + xmpp_stanza_add_child(name, name_prefix); + xmpp_stanza_release(name_prefix); + } + + if (vcard->name.suffix) { + xmpp_stanza_t* name_suffix = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(name_suffix, "SUFFIX"); + + xmpp_stanza_t* name_suffix_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(name_suffix_text, vcard->name.suffix); + xmpp_stanza_add_child(name_suffix, name_suffix_text); + xmpp_stanza_release(name_suffix_text); + + xmpp_stanza_add_child(name, name_suffix); + xmpp_stanza_release(name_suffix); + } + + xmpp_stanza_add_child(vcard_stanza, name); + xmpp_stanza_release(name); + } + + GList* pointer = g_queue_peek_head_link(vcard->elements); + + for (; pointer != NULL; pointer = pointer->next) { + assert(pointer->data != NULL); + vcard_element_t* element = (vcard_element_t*)pointer->data; + + switch (element->type) { + case VCARD_NICKNAME: + { + // <NICKNAME> element + xmpp_stanza_t* nickname = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(nickname, "NICKNAME"); + + xmpp_stanza_t* nickname_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(nickname_text, element->nickname); + xmpp_stanza_add_child(nickname, nickname_text); + xmpp_stanza_release(nickname_text); + + xmpp_stanza_add_child(vcard_stanza, nickname); + xmpp_stanza_release(nickname); + break; + } + case VCARD_PHOTO: + { + // <PHOTO> element + xmpp_stanza_t* photo = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(photo, "PHOTO"); + + if (element->photo.external) { + xmpp_stanza_t* extval = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(extval, "EXTVAL"); + + xmpp_stanza_t* extval_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(extval_text, element->photo.extval); + xmpp_stanza_add_child(extval, extval_text); + xmpp_stanza_release(extval_text); + + xmpp_stanza_add_child(photo, extval); + xmpp_stanza_release(extval); + } else { + xmpp_stanza_t* binval = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(binval, "BINVAL"); + + gchar* base64 = g_base64_encode(element->photo.data, element->photo.length); + xmpp_stanza_t* binval_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(binval_text, base64); + g_free(base64); + xmpp_stanza_add_child(binval, binval_text); + xmpp_stanza_release(binval_text); + + xmpp_stanza_add_child(photo, binval); + xmpp_stanza_release(binval); + + xmpp_stanza_t* type = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(type, "TYPE"); + + xmpp_stanza_t* type_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(type_text, element->photo.type); + xmpp_stanza_add_child(type, type_text); + xmpp_stanza_release(type_text); + + xmpp_stanza_add_child(photo, type); + xmpp_stanza_release(type); + } + + xmpp_stanza_add_child(vcard_stanza, photo); + xmpp_stanza_release(photo); + break; + } + case VCARD_BIRTHDAY: + { + // <BDAY> element + xmpp_stanza_t* birthday = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(birthday, "BDAY"); + + gchar* bday_text = g_date_time_format(element->birthday, "%Y-%m-%d"); + xmpp_stanza_t* birthday_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(birthday_text, bday_text); + g_free(bday_text); + xmpp_stanza_add_child(birthday, birthday_text); + xmpp_stanza_release(birthday_text); + + xmpp_stanza_add_child(vcard_stanza, birthday); + xmpp_stanza_release(birthday); + break; + } + case VCARD_ADDRESS: + { + // <ADR> element + xmpp_stanza_t* address = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(address, "ADR"); + + // Options + if (element->address.options & VCARD_HOME) { + xmpp_stanza_t* home = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(home, "HOME"); + + xmpp_stanza_add_child(address, home); + xmpp_stanza_release(home); + } + if ((element->address.options & VCARD_WORK) == VCARD_WORK) { + xmpp_stanza_t* work = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(work, "WORK"); + + xmpp_stanza_add_child(address, work); + xmpp_stanza_release(work); + } + if ((element->address.options & VCARD_POSTAL) == VCARD_POSTAL) { + xmpp_stanza_t* postal = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(postal, "POSTAL"); + + xmpp_stanza_add_child(address, postal); + xmpp_stanza_release(postal); + } + if ((element->address.options & VCARD_PARCEL) == VCARD_PARCEL) { + xmpp_stanza_t* parcel = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(parcel, "PARCEL"); + + xmpp_stanza_add_child(address, parcel); + xmpp_stanza_release(parcel); + } + if ((element->address.options & VCARD_INTL) == VCARD_INTL) { + xmpp_stanza_t* intl = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(intl, "INTL"); + + xmpp_stanza_add_child(address, intl); + xmpp_stanza_release(intl); + } + if ((element->address.options & VCARD_DOM) == VCARD_DOM) { + xmpp_stanza_t* dom = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(dom, "DOM"); + + xmpp_stanza_add_child(address, dom); + xmpp_stanza_release(dom); + } + if ((element->address.options & VCARD_PREF) == VCARD_PREF) { + xmpp_stanza_t* pref = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pref, "PREF"); + + xmpp_stanza_add_child(address, pref); + xmpp_stanza_release(pref); + } + + if (element->address.pobox) { + xmpp_stanza_t* pobox = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pobox, "POBOX"); + + xmpp_stanza_t* pobox_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(pobox_text, element->address.pobox); + xmpp_stanza_add_child(pobox, pobox_text); + xmpp_stanza_release(pobox_text); + + xmpp_stanza_add_child(address, pobox); + xmpp_stanza_release(pobox); + } + if (element->address.extaddr) { + xmpp_stanza_t* extaddr = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(extaddr, "EXTADD"); + + xmpp_stanza_t* extaddr_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(extaddr_text, element->address.extaddr); + xmpp_stanza_add_child(extaddr, extaddr_text); + xmpp_stanza_release(extaddr_text); + + xmpp_stanza_add_child(address, extaddr); + xmpp_stanza_release(extaddr); + } + if (element->address.street) { + xmpp_stanza_t* street = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(street, "STREET"); + + xmpp_stanza_t* street_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(street_text, element->address.street); + xmpp_stanza_add_child(street, street_text); + xmpp_stanza_release(street_text); + + xmpp_stanza_add_child(address, street); + xmpp_stanza_release(street); + } + if (element->address.locality) { + xmpp_stanza_t* locality = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(locality, "LOCALITY"); + + xmpp_stanza_t* locality_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(locality_text, element->address.locality); + xmpp_stanza_add_child(locality, locality_text); + xmpp_stanza_release(locality_text); + + xmpp_stanza_add_child(address, locality); + xmpp_stanza_release(locality); + } + if (element->address.region) { + xmpp_stanza_t* region = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(region, "REGION"); + + xmpp_stanza_t* region_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(region_text, element->address.region); + xmpp_stanza_add_child(region, region_text); + xmpp_stanza_release(region_text); + + xmpp_stanza_add_child(address, region); + xmpp_stanza_release(region); + } + if (element->address.pcode) { + xmpp_stanza_t* pcode = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pcode, "PCODE"); + + xmpp_stanza_t* pcode_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(pcode_text, element->address.pcode); + xmpp_stanza_add_child(pcode, pcode_text); + xmpp_stanza_release(pcode_text); + + xmpp_stanza_add_child(address, pcode); + xmpp_stanza_release(pcode); + } + if (element->address.country) { + xmpp_stanza_t* ctry = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(ctry, "CTRY"); + + xmpp_stanza_t* ctry_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(ctry_text, element->address.country); + xmpp_stanza_add_child(ctry, ctry_text); + xmpp_stanza_release(ctry_text); + + xmpp_stanza_add_child(address, ctry); + xmpp_stanza_release(ctry); + } + + xmpp_stanza_add_child(vcard_stanza, address); + xmpp_stanza_release(address); + break; + } + case VCARD_TELEPHONE: + { + // <TEL> element + xmpp_stanza_t* tel = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(tel, "TEL"); + + // Options + if (element->telephone.options & VCARD_HOME) { + xmpp_stanza_t* home = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(home, "HOME"); + + xmpp_stanza_add_child(tel, home); + xmpp_stanza_release(home); + } + if ((element->telephone.options & VCARD_WORK) == VCARD_WORK) { + xmpp_stanza_t* work = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(work, "WORK"); + + xmpp_stanza_add_child(tel, work); + xmpp_stanza_release(work); + } + if ((element->telephone.options & VCARD_TEL_VOICE) == VCARD_TEL_VOICE) { + xmpp_stanza_t* voice = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(voice, "VOICE"); + + xmpp_stanza_add_child(tel, voice); + xmpp_stanza_release(voice); + } + if ((element->telephone.options & VCARD_TEL_FAX) == VCARD_TEL_FAX) { + xmpp_stanza_t* fax = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(fax, "FAX"); + + xmpp_stanza_add_child(tel, fax); + xmpp_stanza_release(fax); + } + if ((element->telephone.options & VCARD_TEL_PAGER) == VCARD_TEL_PAGER) { + xmpp_stanza_t* pager = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pager, "PAGER"); + + xmpp_stanza_add_child(tel, pager); + xmpp_stanza_release(pager); + } + if ((element->telephone.options & VCARD_TEL_MSG) == VCARD_TEL_MSG) { + xmpp_stanza_t* msg = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(msg, "MSG"); + + xmpp_stanza_add_child(tel, msg); + xmpp_stanza_release(msg); + } + if ((element->telephone.options & VCARD_TEL_CELL) == VCARD_TEL_CELL) { + xmpp_stanza_t* cell = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(cell, "CELL"); + + xmpp_stanza_add_child(tel, cell); + xmpp_stanza_release(cell); + } + if ((element->telephone.options & VCARD_TEL_VIDEO) == VCARD_TEL_VIDEO) { + xmpp_stanza_t* video = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(video, "VIDEO"); + + xmpp_stanza_add_child(tel, video); + xmpp_stanza_release(video); + } + if ((element->telephone.options & VCARD_TEL_BBS) == VCARD_TEL_BBS) { + xmpp_stanza_t* bbs = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(bbs, "BBS"); + + xmpp_stanza_add_child(tel, bbs); + xmpp_stanza_release(bbs); + } + if ((element->telephone.options & VCARD_TEL_MODEM) == VCARD_TEL_MODEM) { + xmpp_stanza_t* modem = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(modem, "MODEM"); + + xmpp_stanza_add_child(tel, modem); + xmpp_stanza_release(modem); + } + if ((element->telephone.options & VCARD_TEL_ISDN) == VCARD_TEL_ISDN) { + xmpp_stanza_t* isdn = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(isdn, "ISDN"); + + xmpp_stanza_add_child(tel, isdn); + xmpp_stanza_release(isdn); + } + if ((element->telephone.options & VCARD_TEL_PCS) == VCARD_TEL_PCS) { + xmpp_stanza_t* pcs = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pcs, "PCS"); + + xmpp_stanza_add_child(tel, pcs); + xmpp_stanza_release(pcs); + } + if ((element->telephone.options & VCARD_PREF) == VCARD_PREF) { + xmpp_stanza_t* pref = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pref, "PREF"); + + xmpp_stanza_add_child(tel, pref); + xmpp_stanza_release(pref); + } + + if (element->telephone.number) { + xmpp_stanza_t* number = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(number, "NUMBER"); + + xmpp_stanza_t* number_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(number_text, element->telephone.number); + xmpp_stanza_add_child(number, number_text); + xmpp_stanza_release(number_text); + + xmpp_stanza_add_child(tel, number); + xmpp_stanza_release(number); + } + xmpp_stanza_add_child(vcard_stanza, tel); + xmpp_stanza_release(tel); + break; + } + case VCARD_EMAIL: + { + xmpp_stanza_t* email = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(email, "EMAIL"); + + if (element->email.options & VCARD_HOME) { + xmpp_stanza_t* home = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(home, "HOME"); + + xmpp_stanza_add_child(email, home); + xmpp_stanza_release(home); + } + if ((element->email.options & VCARD_WORK) == VCARD_WORK) { + xmpp_stanza_t* work = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(work, "WORK"); + + xmpp_stanza_add_child(email, work); + xmpp_stanza_release(work); + } + if ((element->email.options & VCARD_EMAIL_X400) == VCARD_EMAIL_X400) { + xmpp_stanza_t* x400 = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(x400, "X400"); + + xmpp_stanza_add_child(email, x400); + xmpp_stanza_release(x400); + } + if ((element->email.options & VCARD_PREF) == VCARD_PREF) { + xmpp_stanza_t* pref = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(pref, "PREF"); + + xmpp_stanza_add_child(email, pref); + xmpp_stanza_release(pref); + } + + if (element->email.userid) { + xmpp_stanza_t* userid = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(userid, "USERID"); + + xmpp_stanza_t* userid_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(userid_text, element->email.userid); + xmpp_stanza_add_child(userid, userid_text); + xmpp_stanza_release(userid_text); + + xmpp_stanza_add_child(email, userid); + xmpp_stanza_release(userid); + } + + xmpp_stanza_add_child(vcard_stanza, email); + xmpp_stanza_release(email); + break; + } + case VCARD_JID: + { + xmpp_stanza_t* jid = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(jid, "JABBERID"); + + if (element->jid) { + xmpp_stanza_t* jid_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(jid_text, element->jid); + xmpp_stanza_add_child(jid, jid_text); + xmpp_stanza_release(jid_text); + } + + xmpp_stanza_add_child(vcard_stanza, jid); + xmpp_stanza_release(jid); + break; + } + case VCARD_TITLE: + { + xmpp_stanza_t* title = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(title, "TITLE"); + + if (element->title) { + xmpp_stanza_t* title_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(title_text, element->title); + xmpp_stanza_add_child(title, title_text); + xmpp_stanza_release(title_text); + } + + xmpp_stanza_add_child(vcard_stanza, title); + xmpp_stanza_release(title); + break; + } + case VCARD_ROLE: + { + xmpp_stanza_t* role = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(role, "ROLE"); + + if (element->role) { + xmpp_stanza_t* role_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(role_text, element->role); + xmpp_stanza_add_child(role, role_text); + xmpp_stanza_release(role_text); + } + + xmpp_stanza_add_child(vcard_stanza, role); + xmpp_stanza_release(role); + break; + } + case VCARD_NOTE: + { + xmpp_stanza_t* note = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(note, "NOTE"); + + if (element->note) { + xmpp_stanza_t* note_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(note_text, element->note); + xmpp_stanza_add_child(note, note_text); + xmpp_stanza_release(note_text); + } + + xmpp_stanza_add_child(vcard_stanza, note); + xmpp_stanza_release(note); + break; + } + case VCARD_URL: + { + xmpp_stanza_t* url = xmpp_stanza_new(ctx); + xmpp_stanza_set_name(url, "URL"); + + if (element->url) { + xmpp_stanza_t* url_text = xmpp_stanza_new(ctx); + xmpp_stanza_set_text(url_text, element->url); + xmpp_stanza_add_child(url, url_text); + xmpp_stanza_release(url_text); + } + + xmpp_stanza_add_child(vcard_stanza, url); + xmpp_stanza_release(url); + break; + } + } + } + + return vcard_stanza; +} + +static int +_vcard_print_result(xmpp_stanza_t* const stanza, void* userdata) +{ + _userdata* data = (_userdata*)userdata; + const char* from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + + if (from) { + win_println(data->window, THEME_DEFAULT, "!", "vCard for %s", from); + } else { + win_println(data->window, THEME_DEFAULT, "!", "This account's vCard"); + } + + xmpp_stanza_t* vcard_xml = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_VCARD); + if (!vcard_parse(vcard_xml, data->vcard)) { + return 1; + } + + win_show_vcard(data->window, data->vcard); + + return 1; +} + +void +vcard_print(xmpp_ctx_t* ctx, ProfWin* window, char* jid) +{ + if (!jid && vcard_user && vcard_user->modified) { + win_println(window, THEME_DEFAULT, "!", "This account's vCard (modified, `/vcard upload` to push)"); + win_show_vcard(window, vcard_user); + return; + } + + _userdata* data = calloc(1, sizeof(_userdata)); + data->vcard = vcard_new(); + if (!data || !data->vcard) { + if (data) { + free(data); + } + + cons_show("vCard allocation failed"); + return; + } + + data->window = window; + + char* id = connection_create_stanza_id(); + xmpp_stanza_t* iq = stanza_create_vcard_request_iq(ctx, jid, id); + + iq_id_handler_add(id, _vcard_print_result, (ProfIqFreeCallback)_free_userdata, data); + + free(id); + iq_send_stanza(iq); + xmpp_stanza_release(iq); +} + +static int +_vcard_photo_result(xmpp_stanza_t* const stanza, void* userdata) +{ + _userdata* data = (_userdata*)userdata; + const char* from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM); + if (!from) { + from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TO); + } + + vcard_element_photo_t* photo = NULL; + + xmpp_stanza_t* vcard_xml = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_VCARD); + if (!vcard_parse(vcard_xml, data->vcard)) { + return 1; + } + + if (data->photo_index < 0) { + GList* list_pointer; + for (list_pointer = g_queue_peek_head_link(data->vcard->elements); list_pointer != NULL; list_pointer = list_pointer->next) { + vcard_element_t* element = list_pointer->data; + + if (element->type != VCARD_PHOTO) { + continue; + } else { + photo = &element->photo; + } + } + + if (photo == NULL) { + cons_show_error("No photo was found in vCard"); + return 1; + } + } else { + vcard_element_t* element = (vcard_element_t*)g_queue_peek_nth(data->vcard->elements, data->photo_index); + + if (element == NULL) { + cons_show_error("No element was found at index %d", data->photo_index); + return 1; + } else if (element->type != VCARD_PHOTO) { + cons_show_error("Element is not a photo"); + return 1; + } + + photo = &element->photo; + } + + if (photo->external) { + cons_show_error("Cannot handle external value: %s", photo->extval); + return 1; + } + + GString* filename; + + if (!data->filename) { + char* path = files_get_data_path(DIR_PHOTOS); + filename = g_string_new(path); + free(path); + g_string_append(filename, "/"); + + errno = 0; + int res = g_mkdir_with_parents(filename->str, S_IRWXU); + if (res == -1) { + const char* errmsg = strerror(errno); + if (errmsg) { + cons_show_error("Error creating directory %s: %s", filename->str, errmsg); + g_string_free(filename, TRUE); + return 1; + } else { + cons_show_error("Unknown error creating directory %s", filename->str); + g_string_free(filename, TRUE); + } + } + gchar* from1 = str_replace(from, "@", "_at_"); + gchar* from2 = str_replace(from1, "/", "_slash_"); + g_free(from1); + + g_string_append(filename, from2); + g_free(from2); + } else { + filename = g_string_new(data->filename); + } + + // check a few image types ourselves + // TODO: we could use /etc/mime-types + if (g_strcmp0(photo->type, "image/png") == 0 || g_strcmp0(photo->type, "img/png") == 0) { + g_string_append(filename, ".png"); + } else if (g_strcmp0(photo->type, "image/jpeg") == 0 || g_strcmp0(photo->type, "img/jpeg") == 0) { + g_string_append(filename, ".jpeg"); + } else if (g_strcmp0(photo->type, "image/webp") == 0 || g_strcmp0(photo->type, "img/webp") == 0) { + g_string_append(filename, ".webp"); + } + + GError* err = NULL; + + if (g_file_set_contents(filename->str, (gchar*)photo->data, photo->length, &err) == FALSE) { + cons_show_error("Unable to save photo: %s", err->message); + g_error_free(err); + g_string_free(filename, TRUE); + return 1; + } else { + cons_show("Photo saved as %s", filename->str); + } + + if (data->open) { + gchar** argv; + gint argc; + + gchar* cmdtemplate = prefs_get_string(PREF_VCARD_PHOTO_CMD); + + // this makes it work with filenames that contain spaces + g_string_prepend(filename, "\""); + g_string_append(filename, "\""); + gchar* cmd = str_replace(cmdtemplate, "%p", filename->str); + g_free(cmdtemplate); + + if (g_shell_parse_argv(cmd, &argc, &argv, &err) == FALSE) { + cons_show_error("Failed to parse command template"); + g_free(cmd); + } else { + if (!call_external(argv)) { + cons_show_error("Unable to execute command"); + } + g_strfreev(argv); + } + } + + g_string_free(filename, TRUE); + + return 1; +} + +void +vcard_photo(xmpp_ctx_t* ctx, char* jid, char* filename, int index, gboolean open) +{ + _userdata* data = calloc(1, sizeof(_userdata)); + data->vcard = vcard_new(); + + if (!data || !data->vcard) { + if (data) { + free(data); + } + + cons_show("vCard allocation failed"); + return; + } + + data->photo_index = index; + data->open = open; + + if (filename) { + data->filename = strdup(filename); + } + + char* id = connection_create_stanza_id(); + xmpp_stanza_t* iq = stanza_create_vcard_request_iq(ctx, jid, id); + + iq_id_handler_add(id, _vcard_photo_result, (ProfIqFreeCallback)_free_userdata, data); + + free(id); + iq_send_stanza(iq); + xmpp_stanza_release(iq); +} + +static int +_vcard_refresh_result(xmpp_stanza_t* const stanza, void* userdata) +{ + xmpp_stanza_t* vcard_xml = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_VCARD); + + vcard_free_full(vcard_user); + if (!vcard_parse(vcard_xml, vcard_user)) { + return 1; + } + + cons_show("vCard refreshed"); + vcard_user->modified = FALSE; + return 1; +} + +void +vcard_user_refresh(void) +{ + if (!vcard_user) { + vcard_user = vcard_new(); + } + + if (!vcard_user) { + return; + } + + char* id = connection_create_stanza_id(); + xmpp_stanza_t* iq = stanza_create_vcard_request_iq(connection_get_ctx(), NULL, id); + + iq_id_handler_add(id, _vcard_refresh_result, NULL, NULL); + + free(id); + iq_send_stanza(iq); + xmpp_stanza_release(iq); +} + +// Upload a vCard and set it as the currently connected account's vCard +void +vcard_upload(xmpp_ctx_t* ctx, vCard* vcard) +{ + char* id = connection_create_stanza_id(); + xmpp_stanza_t* iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id); + xmpp_stanza_set_from(iq, connection_get_fulljid()); + + xmpp_stanza_t* vcard_stanza = vcard_to_xml(ctx, vcard); + + if (!vcard_stanza) { + xmpp_stanza_release(iq); + free(id); + return; + } + + xmpp_stanza_add_child(iq, vcard_stanza); + xmpp_stanza_release(vcard_stanza); + + free(id); + iq_send_stanza(iq); + xmpp_stanza_release(iq); +} + +void +vcard_user_save(void) +{ + vcard_upload(connection_get_ctx(), vcard_user); + vcard_user->modified = FALSE; +} + +void +vcard_user_set_fullname(char* fullname) +{ + if (vcard_user->fullname) { + free(vcard_user->fullname); + } + if (fullname) { + vcard_user->fullname = strdup(fullname); + } else { + vcard_user->fullname = NULL; + } + + vcard_user->modified = TRUE; +} + +void +vcard_user_set_name_family(char* family) +{ + if (vcard_user->name.family) { + free(vcard_user->name.family); + } + if (family) { + vcard_user->name.family = strdup(family); + } else { + vcard_user->name.family = NULL; + } + + vcard_user->modified = TRUE; +} + +void +vcard_user_set_name_given(char* given) +{ + if (vcard_user->name.given) { + free(vcard_user->name.given); + } + if (given) { + vcard_user->name.given = strdup(given); + } else { + vcard_user->name.given = NULL; + } + + vcard_user->modified = TRUE; +} + +void +vcard_user_set_name_middle(char* middle) +{ + if (vcard_user->name.middle) { + free(vcard_user->name.middle); + } + if (middle) { + vcard_user->name.middle = strdup(middle); + } else { + vcard_user->name.middle = NULL; + } + + vcard_user->modified = TRUE; +} + +void +vcard_user_set_name_prefix(char* prefix) +{ + if (vcard_user->name.prefix) { + free(vcard_user->name.prefix); + } + if (prefix) { + vcard_user->name.prefix = strdup(prefix); + } else { + vcard_user->name.prefix = NULL; + } + + vcard_user->modified = TRUE; +} + +void +vcard_user_set_name_suffix(char* suffix) +{ + if (vcard_user->name.suffix) { + free(vcard_user->name.suffix); + } + if (suffix) { + vcard_user->name.suffix = strdup(suffix); + } else { + vcard_user->name.suffix = NULL; + } + + vcard_user->modified = TRUE; +} + +void +vcard_user_add_element(vcard_element_t* element) +{ + g_queue_push_tail(vcard_user->elements, element); + vcard_user->modified = TRUE; +} + +void +vcard_user_remove_element(unsigned int index) +{ + void* pointer = g_queue_pop_nth(vcard_user->elements, index); + + if (pointer) { + _free_vcard_element(pointer); + vcard_user->modified = TRUE; + } +} + +vcard_element_t* +vcard_user_get_element_index(unsigned int index) +{ + return g_queue_peek_nth(vcard_user->elements, index); +} + +ProfWin* +vcard_user_create_win(void) +{ + return wins_new_vcard(vcard_user); +} + +void +vcard_user_free(void) +{ + if (vcard_user) { + vcard_free_full(vcard_user); + vcard_free(vcard_user); + } + vcard_user = NULL; +} diff --git a/src/xmpp/vcard.h b/src/xmpp/vcard.h new file mode 100644 index 00000000..9ec04c8c --- /dev/null +++ b/src/xmpp/vcard.h @@ -0,0 +1,171 @@ +/* + * vcard.h + * vim: expandtab:ts=4:sts=4:sw=4 + * + * Copyright (C) 2022 Marouane L. <techmetx11@disroot.org> + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Profanity. If not, see <https://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#ifndef XMPP_VCARD_H +#define XMPP_VCARD_H + +#include <glib.h> + +// 17 bits are currently used, out of (a possible) 32 bits +typedef enum { + VCARD_HOME = 1, + VCARD_WORK = 2, + VCARD_POSTAL = 4, + VCARD_PARCEL = 8, + VCARD_INTL = 16, + VCARD_PREF = 32, + VCARD_TEL_VOICE = 64, + VCARD_TEL_FAX = 128, + VCARD_TEL_PAGER = 256, + VCARD_TEL_MSG = 512, + VCARD_TEL_CELL = 1024, + VCARD_TEL_VIDEO = 2048, + VCARD_TEL_BBS = 4096, + VCARD_TEL_MODEM = 8192, + VCARD_TEL_ISDN = 16384, + VCARD_TEL_PCS = 32768, + VCARD_EMAIL_X400 = 65536, + VCARD_EMAIL_INTERNET = 131072, + VCARD_DOM = 262144 +} vcard_element_options_t; + +typedef struct _vcard_name +{ + char *family, *given, *middle, *prefix, *suffix; +} vcard_name_t; + +typedef struct _vcard_element_photo +{ + union { + struct + { + guchar* data; + char* type; + gsize length; + }; + char* extval; + }; + gboolean external; +} vcard_element_photo_t; + +typedef struct _vcard_element_address +{ + char *pobox, *extaddr, *street, *locality, *region, *pcode, *country; + + // Options used: + // VCARD_HOME + // VCARD_WORK + // VCARD_POSTAL + // VCARD_PARCEL + // VCARD_DOM + // VCARD_INTL + // VCARD_PREF + vcard_element_options_t options; +} vcard_element_address_t; + +typedef struct _vcard_element_telephone +{ + char* number; + + // Options used: + // VCARD_HOME + // VCARD_WORK + // VCARD_TEL_VOICE + // VCARD_TEL_FAX + // VCARD_TEL_PAGER + // VCARD_TEL_MSG + // VCARD_TEL_CELL + // VCARD_TEL_VIDEO + // VCARD_TEL_BBS + // VCARD_TEL_MODEM + // VCARD_TEL_ISDN + // VCARD_TEL_PCS + // VCARD_PREF + vcard_element_options_t options; +} vcard_element_telephone_t; + +typedef struct _vcard_element_email +{ + char* userid; + + // Options used: + // VCARD_HOME + // VCARD_WORK + // VCARD_EMAIL_X400 + // VCARD_PREF + vcard_element_options_t options; +} vcard_element_email_t; + +typedef enum _vcard_element_type { + VCARD_NICKNAME, + VCARD_PHOTO, + VCARD_BIRTHDAY, + VCARD_ADDRESS, + VCARD_TELEPHONE, + VCARD_EMAIL, + VCARD_JID, + VCARD_TITLE, + VCARD_ROLE, + VCARD_NOTE, + VCARD_URL +} vcard_element_type; + +typedef struct _vcard_element +{ + vcard_element_type type; + + union { + char *nickname, *jid, *title, *role, *note, *url; + vcard_element_photo_t photo; + GDateTime* birthday; + vcard_element_address_t address; + vcard_element_telephone_t telephone; + vcard_element_email_t email; + }; +} vcard_element_t; + +typedef struct _vcard +{ + // These elements are only meant to appear once (per DTD) + vcard_name_t name; + char* fullname; + + gboolean modified; + + // GQueue of vcard_element* + GQueue* elements; +} vCard; + +#endif diff --git a/src/xmpp/vcard_funcs.h b/src/xmpp/vcard_funcs.h new file mode 100644 index 00000000..5f4aa3d0 --- /dev/null +++ b/src/xmpp/vcard_funcs.h @@ -0,0 +1,66 @@ +/* + * vcard_funcs.h + * vim: expandtab:ts=4:sts=4:sw=4 + * + * Copyright (C) 2022 Marouane L. <techmetx11@disroot.org> + * + * This file is part of Profanity. + * + * Profanity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Profanity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Profanity. If not, see <https://www.gnu.org/licenses/>. + * + * In addition, as a special exception, the copyright holders give permission to + * link the code of portions of this program with the OpenSSL library under + * certain conditions as described in each individual source file, and + * distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all of the + * code used other than OpenSSL. If you modify file(s) with this exception, you + * may extend this exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. If you delete this exception statement from all + * source files in the program, then also delete it here. + * + */ + +#ifndef XMPP_VCARD_FUNCS_H +#define XMPP_VCARD_FUNCS_H + +#include "ui/win_types.h" +#include "xmpp/vcard.h" + +vCard* vcard_new(); +void vcard_free(vCard* vcard); +void vcard_free_full(vCard* vcard); + +gboolean vcard_parse(xmpp_stanza_t* vcard_xml, vCard* vcard); + +void vcard_print(xmpp_ctx_t* ctx, ProfWin* window, char* jid); +void vcard_photo(xmpp_ctx_t* ctx, char* jid, char* filename, int index, gboolean open); + +void vcard_user_refresh(void); +void vcard_user_save(void); +void vcard_user_set_fullname(char* fullname); +void vcard_user_set_name_family(char* family); +void vcard_user_set_name_given(char* given); +void vcard_user_set_name_middle(char* middle); +void vcard_user_set_name_prefix(char* prefix); +void vcard_user_set_name_suffix(char* suffix); + +void vcard_user_add_element(vcard_element_t* element); +void vcard_user_remove_element(unsigned int index); +vcard_element_t* vcard_user_get_element_index(unsigned int index); +ProfWin* vcard_user_create_win(); + +void vcard_user_free(void); +#endif |