summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2020-11-17 15:07:05 +0300
committerdefanor <defanor@uberspace.net>2020-11-17 15:18:22 +0300
commit48542332c351d21706e61abbd9cb50ce02afac2d (patch)
tree836aa3365f3de12a14bdd7b5ab4188f09eec8815
parent0748958c63052a6c4e0fb20a172ae793fdf8fa6c (diff)
Add initial JID parsing
-rw-r--r--README2
-rw-r--r--examples/basic.c12
-rw-r--r--examples/weechat.c80
-rw-r--r--src/Makefile.am6
-rw-r--r--src/rexmpp.c72
-rw-r--r--src/rexmpp.h6
-rw-r--r--src/rexmpp_jid.c57
-rw-r--r--src/rexmpp_jid.h24
8 files changed, 150 insertions, 109 deletions
diff --git a/README b/README
index 16644cc..f8c38de 100644
--- a/README
+++ b/README
@@ -46,7 +46,7 @@ A rough roadmap:
[.] Doxygen documentation.
[.] Texinfo manual.
-[ ] Proper JID handling (RFC 7622).
+[.] Proper JID handling (RFC 7622).
[ ] Abstraction of the used XML, SASL, TLS, and DNS libraries, and
optional usage of alternative ones. Though maybe shouldn't
abstract out XML functions and structures: could reuse existing
diff --git a/examples/basic.c b/examples/basic.c
index 88a1ca4..09a98c4 100644
--- a/examples/basic.c
+++ b/examples/basic.c
@@ -37,16 +37,8 @@ int my_sasl_property_cb (rexmpp_t *s, Gsasl_property prop) {
return GSASL_OK;
}
if (prop == GSASL_AUTHID) {
- char *domainpart = strchr(s->initial_jid, '@');
- if (domainpart != NULL) {
- int localpart_len = domainpart - s->initial_jid;
- char *localpart = malloc(localpart_len + 1);
- localpart[localpart_len] = 0;
- strncpy(localpart, s->initial_jid, localpart_len);
- gsasl_property_set (s->sasl_session, GSASL_AUTHID, localpart);
- free(localpart);
- return GSASL_OK;
- }
+ gsasl_property_set (s->sasl_session, GSASL_AUTHID, s->initial_jid.local);
+ return GSASL_OK;
}
printf("unhandled gsasl property: %d\n", prop);
return GSASL_NO_CALLBACK;
diff --git a/examples/weechat.c b/examples/weechat.c
index dc6c957..46b9093 100644
--- a/examples/weechat.c
+++ b/examples/weechat.c
@@ -28,6 +28,7 @@
#include "weechat-plugin.h"
#include "rexmpp.h"
#include "rexmpp_roster.h"
+#include "rexmpp_jid.h"
WEECHAT_PLUGIN_NAME("rexmpp");
WEECHAT_PLUGIN_DESCRIPTION("XMPP plugin using librexmpp");
@@ -80,16 +81,8 @@ int my_sasl_property_cb (rexmpp_t *s, Gsasl_property prop) {
return GSASL_OK;
}
if (prop == GSASL_AUTHID) {
- char *domainpart = strchr(s->initial_jid, '@');
- if (domainpart != NULL) {
- int localpart_len = domainpart - s->initial_jid;
- char *localpart = malloc(localpart_len + 1);
- localpart[localpart_len] = 0;
- strncpy(localpart, s->initial_jid, localpart_len);
- gsasl_property_set (s->sasl_session, GSASL_AUTHID, localpart);
- free(localpart);
- return GSASL_OK;
- }
+ gsasl_property_set (s->sasl_session, GSASL_AUTHID, s->initial_jid.local);
+ return GSASL_OK;
}
weechat_printf(wr->server_buffer, "unhandled gsasl property: %d\n", prop);
return GSASL_NO_CALLBACK;
@@ -137,7 +130,7 @@ int muc_close_cb (const void *ptr, void *data,
struct weechat_rexmpp_muc *wrm = (void*)ptr;
rexmpp_t *s = &wrm->wr->rexmpp_state;
xmlNodePtr presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence"));
- xmlNewProp(presence, "from", s->assigned_jid);
+ xmlNewProp(presence, "from", s->assigned_jid.full);
xmlNewProp(presence, "to", wrm->jid);
xmlNewProp(presence, "type", "unavailable");
rexmpp_send(s, presence);
@@ -152,27 +145,21 @@ int my_xml_in_cb (rexmpp_t *s, xmlNodePtr node) {
/* free(xml_buf); */
if (rexmpp_xml_match(node, "jabber:client", "message")) {
char *from = xmlGetProp(node, "from");
- char *display_name = from;
- int i, resource_removed = 0;
- for (i = 0; i < strlen(from); i++) {
- if (from[i] == '/') {
- from[i] = 0;
- display_name = from + i + 1;
- resource_removed = 1;
- break;
- }
- }
if (from != NULL) {
- struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", from);
+ struct rexmpp_jid from_jid;
+ rexmpp_jid_parse(from, &from_jid);
+ xmlFree(from);
+ char *display_name = from_jid.full;
+ if (from_jid.resource[0]) {
+ display_name = from_jid.resource;
+ }
+ struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", from_jid.bare);
if (buf == NULL) {
- buf = weechat_buffer_new (from,
+ buf = weechat_buffer_new (from_jid.bare,
&query_input_cb, wr, NULL,
&query_close_cb, wr, NULL);
weechat_buffer_set(buf, "nicklist", "1");
}
- if (resource_removed) {
- from[i] = '/'; /* restore */
- }
xmlNodePtr body = rexmpp_xml_find_child(node, "jabber:client", "body");
if (body != NULL) {
xmlChar *str = xmlNodeGetContent(body);
@@ -183,40 +170,33 @@ int my_xml_in_cb (rexmpp_t *s, xmlNodePtr node) {
xmlFree(str);
}
}
- xmlFree(from);
}
}
if (rexmpp_xml_match(node, "jabber:client", "presence")) {
char *presence_type = xmlGetProp(node, "type");
- char *jid = xmlGetProp(node, "from");
- char *full_jid = strdup(jid);
- int i;
- char *resource = "";
- for (i = 0; i < strlen(jid); i++) {
- if (jid[i] == '/') {
- jid[i] = 0;
- resource = jid + i + 1;
- break;
- }
- }
+ char *from = xmlGetProp(node, "from");
+ struct rexmpp_jid from_jid;
+ rexmpp_jid_parse(from, &from_jid);
+ xmlFree(from);
+
if (rexmpp_xml_find_child(node, "http://jabber.org/protocol/muc#user", "x")) {
/* Update MUC nicklist */
- struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", jid);
+ struct t_gui_buffer *buf = weechat_buffer_search("rexmpp", from_jid.bare);
if (buf != NULL) {
if (presence_type != NULL && strcmp(presence_type, "unavailable") == 0) {
struct t_gui_nick *nick =
- weechat_nicklist_search_nick(buf, NULL, resource);
+ weechat_nicklist_search_nick(buf, NULL, from_jid.resource);
if (nick != NULL) {
weechat_nicklist_remove_nick(buf, nick);
}
} else {
- weechat_nicklist_add_nick(buf, NULL, resource,
+ weechat_nicklist_add_nick(buf, NULL, from_jid.resource,
"bar_fg", "", "lightgreen", 1);
}
}
- } else if (rexmpp_roster_find_item(s, jid, NULL) != NULL) {
+ } else if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL) {
/* A roster item. */
- struct t_gui_nick *nick = weechat_nicklist_search_nick(wr->server_buffer, NULL, jid);
+ struct t_gui_nick *nick = weechat_nicklist_search_nick(wr->server_buffer, NULL, from_jid.bare);
if (presence_type == NULL) {
/* An "available" presence: just ensure that it's shown as
online. */
@@ -228,25 +208,23 @@ int my_xml_in_cb (rexmpp_t *s, xmlNodePtr node) {
just went offline). */
xmlNodePtr cur;
int found = 0;
+ struct rexmpp_jid cur_from_jid;
for (cur = s->roster_presence;
cur != NULL;
cur = xmlNextElementSibling(cur)) {
char *cur_from = xmlGetProp(cur, "from");
- if (strcmp(cur_from, full_jid) != 0 &&
- strncmp(cur_from, jid, strlen(jid)) == 0 &&
- strlen(cur_from) > strlen(jid) &&
- cur_from[strlen(jid)] == '/') {
+ rexmpp_jid_parse(cur_from, &cur_from_jid);
+ xmlFree(cur_from);
+ if (strcmp(cur_from_jid.bare, from_jid.bare) == 0 &&
+ strcmp(cur_from_jid.resource, from_jid.resource) != 0) {
found = 1;
}
- free(cur_from);
}
if (! found) {
weechat_nicklist_nick_set(wr->server_buffer, nick, "prefix", "");
}
}
}
- free(jid);
- free(full_jid);
if (presence_type != NULL) {
free(presence_type);
}
@@ -295,7 +273,7 @@ my_input_cb (const void *ptr, void *data,
} else if (input_data[0] == 'j' && input_data[1] == ' ') {
char *jid = strdup(input_data + 2);
xmlNodePtr presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence"));
- xmlNewProp(presence, "from", s->assigned_jid);
+ xmlNewProp(presence, "from", s->assigned_jid.full);
xmlNewProp(presence, "to", jid);
xmlNodePtr x = xmlNewNode(NULL, "x");
xmlNewNs(x, "http://jabber.org/protocol/muc", NULL);
diff --git a/src/Makefile.am b/src/Makefile.am
index 3a977cd..e66a276 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -12,8 +12,10 @@ librexmpp_la_SOURCES = rexmpp_roster.h rexmpp_roster.c \
rexmpp_tcp.h rexmpp_tcp.c \
rexmpp_socks.h rexmpp_socks.c \
rexmpp.h rexmpp.c \
- rexmpp_dns.h rexmpp_dns.c
-include_HEADERS = rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h rexmpp_dns.h
+ rexmpp_dns.h rexmpp_dns.c \
+ rexmpp_jid.h rexmpp_jid.c
+include_HEADERS = rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h \
+ rexmpp_dns.h rexmpp_jid.h
librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) $(GNUTLS_CFLAGS) \
$(LIBDANE_CFLAGS) $(GSASL_CFLAGS) $(UNBOUND_CFLAGS)
librexmpp_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(LIBDANE_LIBS) \
diff --git a/src/rexmpp.c b/src/rexmpp.c
index f6bf7c3..b0d8d94 100644
--- a/src/rexmpp.c
+++ b/src/rexmpp.c
@@ -28,6 +28,7 @@
#include "rexmpp_socks.h"
#include "rexmpp_roster.h"
#include "rexmpp_dns.h"
+#include "rexmpp_jid.h"
void rexmpp_sax_start_elem_ns (rexmpp_t *s,
const char *localname,
@@ -268,8 +269,8 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid)
s->reconnect_number = 0;
s->next_reconnect_time.tv_sec = 0;
s->next_reconnect_time.tv_usec = 0;
- s->initial_jid = NULL;
- s->assigned_jid = NULL;
+ s->initial_jid.full[0] = '\0';
+ s->assigned_jid.full[0] = '\0';
s->stanza_queue_size = 1024;
s->send_queue_size = 1024;
s->iq_queue_size = 1024;
@@ -287,7 +288,10 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid)
return REXMPP_E_JID;
}
- s->initial_jid = strdup(jid);
+ if (rexmpp_jid_parse(jid, &(s->initial_jid))) {
+ rexmpp_log(s, LOG_CRIT, "Failed to parse the initial JID.");
+ return REXMPP_E_JID;
+ }
s->xml_parser = xmlCreatePushParserCtxt(&sax, s, "", 0, NULL);
@@ -422,10 +426,6 @@ void rexmpp_done (rexmpp_t *s) {
s->resolver_ctx = NULL;
}
xmlFreeParserCtxt(s->xml_parser);
- if (s->initial_jid != NULL) {
- free(s->initial_jid);
- s->initial_jid = NULL;
- }
if (s->stream_id != NULL) {
free(s->stream_id);
s->stream_id = NULL;
@@ -565,8 +565,8 @@ xmlNodePtr rexmpp_xml_set_delay (rexmpp_t *s, xmlNodePtr node) {
strftime(buf, 42, "%FT%T%z", local_time);
xmlNodePtr delay = xmlNewChild(node, NULL, "delay", NULL);
xmlNewProp(delay, "stamp", buf);
- if (s != NULL && s->assigned_jid != NULL) {
- xmlNewProp(delay, "from", s->assigned_jid);
+ if (s != NULL && s->assigned_jid.full[0]) {
+ xmlNewProp(delay, "from", s->assigned_jid.full);
}
return node;
}
@@ -777,8 +777,8 @@ void rexmpp_iq_reply (rexmpp_t *s,
xmlNewProp(iq_stanza, "to", to);
free(to);
}
- if (s->assigned_jid != NULL) {
- xmlNewProp(iq_stanza, "from", s->assigned_jid);
+ if (s->assigned_jid.full[0]) {
+ xmlNewProp(iq_stanza, "from", s->assigned_jid.full);
}
if (payload != NULL) {
xmlAddChild(iq_stanza, payload);
@@ -815,8 +815,8 @@ rexmpp_err_t rexmpp_iq_new (rexmpp_t *s,
if (to != NULL) {
xmlNewProp(iq_stanza, "to", to);
}
- if (s->assigned_jid != NULL) {
- xmlNewProp(iq_stanza, "from", s->assigned_jid);
+ if (s->assigned_jid.full[0]) {
+ xmlNewProp(iq_stanza, "from", s->assigned_jid.full);
}
xmlAddChild(iq_stanza, payload);
rexmpp_iq_t *iq = malloc(sizeof(rexmpp_iq_t));
@@ -935,7 +935,7 @@ rexmpp_err_t rexmpp_stream_open (rexmpp_t *s) {
"<stream:stream to='%s' version='1.0' "
"xml:lang='en' xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams'>",
- jid_bare_to_host(s->initial_jid));
+ s->initial_jid.domain);
s->stream_state = REXMPP_STREAM_OPENING;
return rexmpp_send_raw(s, buf, strlen(buf));
}
@@ -1066,7 +1066,7 @@ rexmpp_err_t rexmpp_tls_handshake (rexmpp_t *s) {
}
ret = gnutls_certificate_verify_peers3(s->gnutls_session,
- jid_bare_to_host(s->initial_jid),
+ s->initial_jid.domain,
&status);
if (ret || status) {
s->tls_state = REXMPP_TLS_ERROR;
@@ -1131,8 +1131,8 @@ rexmpp_err_t rexmpp_tls_start (rexmpp_t *s) {
gnutls_session_set_ptr(s->gnutls_session, s);
gnutls_alpn_set_protocols(s->gnutls_session, &xmpp_client_protocol, 1, 0);
gnutls_server_name_set(s->gnutls_session, GNUTLS_NAME_DNS,
- jid_bare_to_host(s->initial_jid),
- strlen(jid_bare_to_host(s->initial_jid)));
+ s->initial_jid.domain,
+ strlen(s->initial_jid.domain));
gnutls_set_default_priority(s->gnutls_session);
gnutls_credentials_set(s->gnutls_session, GNUTLS_CRD_CERTIFICATE,
s->gnutls_cred);
@@ -1247,7 +1247,7 @@ void rexmpp_srv_cb (void *s_ptr,
if (s->server_srv == NULL && s->server_srv_tls == NULL) {
/* Failed to resolve anything: a fallback. */
- s->server_host = jid_bare_to_host(s->initial_jid);
+ s->server_host = s->initial_jid.domain;
s->server_port = 5222;
rexmpp_start_connecting(s);
} else {
@@ -1375,7 +1375,7 @@ void rexmpp_stream_is_ready(rexmpp_t *s) {
if (s->enable_service_discovery) {
xmlNodePtr disco_query = xmlNewNode(NULL, "query");
xmlNewNs(disco_query, "http://jabber.org/protocol/disco#info", NULL);
- rexmpp_iq_new(s, "get", jid_bare_to_host(s->initial_jid),
+ rexmpp_iq_new(s, "get", s->initial_jid.domain,
disco_query, rexmpp_iq_discovery_info);
}
if (s->manage_roster) {
@@ -1420,8 +1420,7 @@ void rexmpp_bound (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response, int success
xmlNodePtr jid = xmlFirstElementChild(child);
if (rexmpp_xml_match(jid, "urn:ietf:params:xml:ns:xmpp-bind", "jid")) {
rexmpp_log(s, LOG_INFO, "jid: %s", xmlNodeGetContent(jid));
- s->assigned_jid = malloc(strlen(xmlNodeGetContent(jid)) + 1);
- strcpy(s->assigned_jid, xmlNodeGetContent(jid));
+ rexmpp_jid_parse(xmlNodeGetContent(jid), &(s->assigned_jid));
}
if (s->stream_id == NULL &&
(child = rexmpp_xml_find_child(s->stream_features, "urn:xmpp:sm:3",
@@ -1523,7 +1522,7 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) {
if (from == NULL) {
from_server = 1;
} else {
- if (strcmp(from, jid_bare_to_host(s->assigned_jid)) == 0) {
+ if (strcmp(from, s->initial_jid.domain) == 0) {
from_server = 1;
}
free(from);
@@ -1597,21 +1596,11 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) {
s->track_roster_presence) {
char *from = xmlGetProp(elem, "from");
if (from != NULL) {
- size_t i;
- int resource_removed = 0;
- for (i = 0; i < strlen(from); i++) {
- if (from[i] == '/') {
- from[i] = '\0';
- resource_removed = i;
- break;
- }
- }
- if (rexmpp_roster_find_item(s, from, NULL) != NULL) {
+ struct rexmpp_jid from_jid;
+ rexmpp_jid_parse(from, &from_jid);
+ xmlFree(from);
+ if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL) {
/* The bare JID is in the roster. */
- if (resource_removed) {
- /* Restore full JID. */
- from[resource_removed] = '/';
- }
char *type = xmlGetProp(elem, "type");
xmlNodePtr cur, prev;
if (type == NULL || strcmp(type, "unavailable") == 0) {
@@ -1622,7 +1611,7 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) {
cur != NULL;
prev = cur, cur = xmlNextElementSibling(cur)) {
char *cur_from = xmlGetProp(cur, "from");
- if (strcmp(cur_from, from) == 0) {
+ if (strcmp(cur_from, from_jid.full) == 0) {
if (prev == NULL) {
s->roster_presence = cur->next;
} else {
@@ -1643,7 +1632,6 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) {
free(type);
}
}
- free(from);
}
}
@@ -2023,12 +2011,12 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
if (s->manual_host == NULL) {
/* Start by querying SRV records. */
rexmpp_log(s, LOG_DEBUG, "start (or reconnect)");
- size_t srv_query_buf_len = strlen(jid_bare_to_host(s->initial_jid)) +
+ size_t srv_query_buf_len = strlen(s->initial_jid.domain) +
strlen("_xmpps-client._tcp..") +
1;
char *srv_query = malloc(srv_query_buf_len);
snprintf(srv_query, srv_query_buf_len,
- "_xmpps-client._tcp.%s.", jid_bare_to_host(s->initial_jid));
+ "_xmpps-client._tcp.%s.", s->initial_jid.domain);
int err;
err = ub_resolve_async(s->resolver_ctx, srv_query, 33, 1,
s, rexmpp_srv_cb, NULL);
@@ -2037,7 +2025,7 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
srv_query, ub_strerror(err));
}
snprintf(srv_query, srv_query_buf_len,
- "_xmpp-client._tcp.%s.", jid_bare_to_host(s->initial_jid));
+ "_xmpp-client._tcp.%s.", s->initial_jid.domain);
err = ub_resolve_async(s->resolver_ctx, srv_query, 33, 1,
s, rexmpp_srv_cb, NULL);
if (err) {
@@ -2104,7 +2092,7 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
s->ping_requested = 1;
xmlNodePtr ping_cmd = xmlNewNode(NULL, "ping");
xmlNewNs(ping_cmd, "urn:xmpp:ping", NULL);
- rexmpp_iq_new(s, "get", jid_bare_to_host(s->initial_jid),
+ rexmpp_iq_new(s, "get", s->initial_jid.domain,
ping_cmd, rexmpp_pong);
} else {
rexmpp_log(s, LOG_WARNING, "Ping timeout, reconnecting.");
diff --git a/src/rexmpp.h b/src/rexmpp.h
index 38cf4b2..8b5632a 100644
--- a/src/rexmpp.h
+++ b/src/rexmpp.h
@@ -17,7 +17,7 @@
#include "rexmpp_tcp.h"
#include "rexmpp_socks.h"
#include "rexmpp_dns.h"
-
+#include "rexmpp_jid.h"
typedef struct rexmpp rexmpp_t;
@@ -221,7 +221,7 @@ struct rexmpp
enum carbons_st carbons_state;
/* Basic configuration. */
- char *initial_jid;
+ struct rexmpp_jid initial_jid;
/* Manual host/port configuration. */
const char *manual_host;
@@ -255,7 +255,7 @@ struct rexmpp
roster_modify_cb_t roster_modify_cb;
/* Stream-related state. */
- char *assigned_jid;
+ struct rexmpp_jid assigned_jid;
xmlNodePtr stream_features;
xmlNodePtr roster_items;
char *roster_ver;
diff --git a/src/rexmpp_jid.c b/src/rexmpp_jid.c
new file mode 100644
index 0000000..44e2754
--- /dev/null
+++ b/src/rexmpp_jid.c
@@ -0,0 +1,57 @@
+/**
+ @file rexmpp_jid.c
+ @brief JID parsing and manipulation
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+
+#include <stddef.h>
+#include <string.h>
+#include "rexmpp_jid.h"
+
+int rexmpp_jid_parse (const char *str, struct rexmpp_jid *jid) {
+ const char *resource = NULL, *domain = NULL;
+ size_t i;
+ size_t resource_len = 0, local_len = 0;
+ size_t domain_len, bare_len, full_len = strlen(str);
+ domain_len = full_len;
+ bare_len = full_len;
+
+ /* Find the separators. */
+ for (i = 0; i < full_len; i++) {
+ if (local_len == 0 && str[i] == '@') {
+ local_len = i;
+ domain_len -= local_len + 1;
+ domain = str + i + 1;
+ }
+ if (str[i] == '/') {
+ resource_len = full_len - i - 1;
+ domain_len -= resource_len + 1;
+ bare_len -= resource_len + 1;
+ resource = str + i + 1;
+ break;
+ }
+ }
+
+ /* Check all the lengths. */
+ if (full_len > 3071 || bare_len > 2047 ||
+ local_len > 1023 || resource_len > 1023 ||
+ domain_len > 1023 || domain_len < 1) {
+ return -1;
+ }
+
+ /* Copy all the parts. */
+ strncpy(jid->full, str, full_len);
+ jid->full[full_len] = '\0';
+ strncpy(jid->bare, str, bare_len);
+ jid->bare[bare_len] = '\0';
+ strncpy(jid->local, str, local_len);
+ jid->local[local_len] = '\0';
+ strncpy(jid->domain, domain, domain_len);
+ jid->domain[domain_len] = '\0';
+ strncpy(jid->resource, resource, resource_len);
+ jid->resource[resource_len] = '\0';
+
+ return 0;
+}
diff --git a/src/rexmpp_jid.h b/src/rexmpp_jid.h
new file mode 100644
index 0000000..8613e3d
--- /dev/null
+++ b/src/rexmpp_jid.h
@@ -0,0 +1,24 @@
+/**
+ @file rexmpp_jid.h
+ @brief JID parsing and manipulation
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+
+#ifndef REXMPP_JID_H
+#define REXMPP_JID_H
+
+/** @brief A redundant structure for easy access to JID parts and
+ avoidance of dynamic allocations. */
+struct rexmpp_jid {
+ char local[1024];
+ char domain[1024];
+ char resource[1024];
+ char bare[2048];
+ char full[3072];
+};
+
+int rexmpp_jid_parse (const char *str, struct rexmpp_jid *jid);
+
+#endif