ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
xmpp.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file xmpp.cpp
6 * @brief XMPP stanza codec (see xmpp.h).
7 */
8
10
11#if PC_ENABLE_XMPP
12
13#include <string.h>
14
15namespace
16{
17// Append a literal to out[*n], bounded. Returns false on overflow.
18bool put(char *out, size_t cap, size_t *n, const char *s)
19{
20 size_t sl = strnlen(s, cap + 1);
21 if (*n + sl >= cap)
22 {
23 return false;
24 }
25 memcpy(out + *n, s, sl);
26 *n += sl;
27 return true;
28}
29
30// Append s, XML-escaped, into out[*n]. Returns false on overflow.
31bool put_escaped(char *out, size_t cap, size_t *n, const char *s)
32{
33 for (; *s; s++)
34 {
35 const char *rep = nullptr;
36 switch (*s)
37 {
38 case '&':
39 rep = "&amp;";
40 break;
41 case '<':
42 rep = "&lt;";
43 break;
44 case '>':
45 rep = "&gt;";
46 break;
47 case '\'':
48 rep = "&apos;";
49 break;
50 case '"':
51 rep = "&quot;";
52 break;
53 default:
54 break;
55 }
56 if (rep)
57 {
58 if (!put(out, cap, n, rep))
59 {
60 return false;
61 }
62 }
63 else
64 {
65 if (*n + 1 >= cap)
66 {
67 return false;
68 }
69 out[(*n)++] = *s;
70 }
71 }
72 return true;
73}
74
75// Append `attr="value"` (value escaped) when value is non-null.
76bool put_attr(char *out, size_t cap, size_t *n, const char *attr, const char *value)
77{
78 if (!value)
79 {
80 return true;
81 }
82 return put(out, cap, n, " ") && put(out, cap, n, attr) && put(out, cap, n, "=\"") &&
83 put_escaped(out, cap, n, value) && put(out, cap, n, "\"");
84}
85
86size_t finish(char *out, size_t n, bool ok)
87{
88 if (!ok)
89 {
90 return 0;
91 }
92 out[n] = '\0';
93 return n;
94}
95} // namespace
96
97size_t pc_xmpp_escape(const char *in, size_t in_len, char *out, size_t cap)
98{
99 if (!in || !out)
100 {
101 return 0;
102 }
103 size_t n = 0;
104 for (size_t i = 0; i < in_len; i++)
105 {
106 char one[2] = {in[i], '\0'};
107 if (!put_escaped(out, cap, &n, one))
108 {
109 return 0;
110 }
111 }
112 return finish(out, n, true);
113}
114
115size_t pc_xmpp_stream_open(const char *from, const char *to, char *out, size_t cap)
116{
117 size_t n = 0;
118 bool ok =
119 put(out, cap, &n, "<?xml version='1.0'?><stream:stream") && put_attr(out, cap, &n, "from", from) &&
120 put_attr(out, cap, &n, "to", to) &&
121 put(out, cap, &n, " xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
122 return finish(out, n, ok);
123}
124
125size_t pc_xmpp_message(const char *to, const char *from, const char *type, const char *body, char *out, size_t cap)
126{
127 size_t n = 0;
128 bool ok = put(out, cap, &n, "<message") && put_attr(out, cap, &n, "to", to) &&
129 put_attr(out, cap, &n, "from", from) && put_attr(out, cap, &n, "type", type) && put(out, cap, &n, ">");
130 if (ok && body)
131 {
132 ok = put(out, cap, &n, "<body>") && put_escaped(out, cap, &n, body) && put(out, cap, &n, "</body>");
133 }
134 ok = ok && put(out, cap, &n, "</message>");
135 return finish(out, n, ok);
136}
137
138size_t pc_xmpp_presence(const char *type, char *out, size_t cap)
139{
140 size_t n = 0;
141 bool ok = put(out, cap, &n, "<presence") && put_attr(out, cap, &n, "type", type) && put(out, cap, &n, "/>");
142 return finish(out, n, ok);
143}
144
145size_t pc_xmpp_iq(const char *type, const char *id, const char *child_xml, char *out, size_t cap)
146{
147 size_t n = 0;
148 bool ok = put(out, cap, &n, "<iq") && put_attr(out, cap, &n, "type", type) && put_attr(out, cap, &n, "id", id) &&
149 put(out, cap, &n, ">");
150 if (ok && child_xml)
151 {
152 ok = put(out, cap, &n, child_xml);
153 }
154 ok = ok && put(out, cap, &n, "</iq>");
155 return finish(out, n, ok);
156}
157
158size_t pc_xmpp_stanza_name(const char *xml, size_t len, char *out, size_t cap)
159{
160 if (!xml || !out || cap == 0)
161 {
162 return 0;
163 }
164 for (size_t i = 0; i + 1 < len; i++)
165 {
166 if (xml[i] != '<')
167 {
168 continue;
169 }
170 char c = xml[i + 1];
171 if (c == '?' || c == '!' || c == '/') // skip declaration / comment / close tag
172 {
173 continue;
174 }
175 size_t j = i + 1, k = 0;
176 while (j < len && xml[j] != ' ' && xml[j] != '>' && xml[j] != '/' && xml[j] != '\t' && xml[j] != '\n')
177 {
178 if (k + 1 >= cap)
179 {
180 return 0;
181 }
182 out[k++] = xml[j++];
183 }
184 out[k] = '\0';
185 return k;
186 }
187 return 0;
188}
189
190size_t pc_xmpp_attr(const char *xml, size_t len, const char *attr, char *out, size_t cap)
191{
192 if (!xml || !attr || !out || cap == 0)
193 {
194 return 0;
195 }
196 size_t al = strnlen(attr, len + 1);
197 // Search only within the first start tag (up to the first '>').
198 size_t end = 0;
199 while (end < len && xml[end] != '>')
200 {
201 end++;
202 }
203 for (size_t i = 0; i + al + 2 < end; i++)
204 {
205 // match <space>attr= (leading space avoids a substring hit inside another attr name)
206 if (!((i == 0 || xml[i - 1] == ' ') && strncmp(xml + i, attr, al) == 0 && xml[i + al] == '='))
207 {
208 continue;
209 }
210 char q = xml[i + al + 1];
211 if (q != '"' && q != '\'')
212 {
213 return 0;
214 }
215 size_t j = i + al + 2, k = 0;
216 while (j < end && xml[j] != q)
217 {
218 if (k + 1 >= cap)
219 {
220 return 0;
221 }
222 out[k++] = xml[j++];
223 }
224 out[k] = '\0';
225 return k;
226 }
227 return 0;
228}
229
230#endif // PC_ENABLE_XMPP
XMPP (RFC 6120) stanza codec (PC_ENABLE_XMPP).