1/*!
2 * @file File: mam.c
3 *
4 * vim: expandtab:ts=2:sts=2:sw=2
5 *
6 * @copyright
7 *
8 * Copyright (C) 2020 Anoxinon e.V.
9 *
10 * This file is part of xmppc.
11 *
12 * xmppc is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation, either version 3 of the License, or
15 * (at your option) any later version.
16 *
17 * xmppc is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with Foobar. If not, see <http://www.gnu.org/licenses/>.
24 *
25 * German
26 *
27 * Diese Datei ist Teil von xmppc.
28 *
29 * xmppc ist Freie Software: Sie können es unter den Bedingungen
30 * der GNU General Public License, wie von der Free Software Foundation,
31 * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren
32 * veröffentlichten Version, weiter verteilen und/oder modifizieren.
33 *
34 * xmppc wird in der Hoffnung, dass es nützlich sein wird, aber
35 * OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
36 * Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
37 * Siehe die GNU General Public License für weitere Details.
38 *
39 * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
40 * Programm erhalten haben. Wenn nicht, siehe <https://www.gnu.org/licenses/>.
41 */
42
43#include "mam.h"
44#include "glib.h"
45#include "stdbool.h"
46#include "stdio.h"
47#include "stdlib.h"
48#include "string.h"
49
50#define MAM_NS "urn:xmpp:mam:2"
51#define FORWARD_NS "urn:xmpp:forward:0"
52#define DELAY_NS "urn:xmpp:delay"
53#define RSM_NS "http://jabber.org/protocol/rsm"
54
55#define MAM_DEFAULT_MAX 5
56#define MAM_MAX_CAP 50
57#define MAM_DEFAULT_TIMEOUT_SECONDS 300
58#define MAM_POLL_PERIOD_MS 2000
59
60typedef struct {
61 char *with;
62 char *start;
63 char *end;
64 char *after_id;
65 char *before_id;
66 char *ids;
67 int max;
68 int timeout;
69 bool has_narrowing;
70} mam_options_t;
71
72typedef struct {
73 xmppc_t *xmppc;
74 mam_options_t options;
75 bool receive;
76 int messages_total;
77 gint64 deadline_us;
78 char *queryid;
79 bool done;
80} mam_run_t;
81
82static void _mam_options_init(mam_options_t *options);
83static void _mam_options_free(mam_options_t *options);
84static bool _mam_parse_terms(xmppc_t *xmppc, int argc, char *argv[], mam_options_t *options, bool receive);
85static void _mam_start_request(xmppc_t *xmppc, mam_options_t *options, bool pretty, bool receive);
86static void _mam_send_request(mam_run_t *run);
87static int _mam_finish(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata);
88static int _mam_receive_poll(xmpp_conn_t *const conn, void *const userdata);
89static int _mam_receive_timeout(xmpp_conn_t *const conn, void *const userdata);
90static int _mam_display_message(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata);
91static int _mam_display_pretty_message(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata);
92static void _mam_disconnect_and_free(xmpp_conn_t *conn, mam_run_t *run, bool delete_id_handler);
93static void _mam_add_field(xmpp_ctx_t *ctx, xmpp_stanza_t *x, const char *var, const char *value, const char *type);
94static void _mam_add_ids_field(xmpp_ctx_t *ctx, xmpp_stanza_t *x, const char *ids);
95static void _mam_add_text_child(xmpp_ctx_t *ctx, xmpp_stanza_t *parent, const char *name, const char *text);
96static bool _mam_set_string_option(xmppc_t *xmppc, char **target, const char *key, const char *value, bool allow_empty);
97static bool _mam_parse_max(xmppc_t *xmppc, mam_options_t *options, const char *value);
98static bool _mam_parse_timeout(xmppc_t *xmppc, mam_options_t *options, const char *value);
99static bool _mam_parse_duration_seconds(const char *value, gint64 *seconds);
100static char *_mam_normalize_datetime(const char *value);
101static bool _mam_result_matches(mam_run_t *run, xmpp_stanza_t *result);
102static void _mam_print_compact_message(xmpp_stanza_t *result);
103static void _mam_print_escaped_attr(const char *text);
104static void _mam_print_escaped_text(const char *text);
105static void _mam_print_escaped(const char *text, bool attribute);
106static void _mam_usage(xmppc_t *xmppc);
107
108void mam_execute_command (xmppc_t *xmppc, int argc, char *argv[]) {
109 mam_options_t options;
110
111 if(argc >= 1) {
112 if (strcmp("list", argv[0]) == 0) {
113 if (_mam_parse_terms(xmppc, argc - 1, argv + 1, &options, false)) {
114 _mam_start_request(xmppc, &options, false, false);
115 } else {
116 xmpp_disconnect(xmppc->conn);
117 }
118 return;
119 } else if (strcmp("pretty", argv[0]) == 0) {
120 if (_mam_parse_terms(xmppc, argc - 1, argv + 1, &options, false)) {
121 _mam_start_request(xmppc, &options, true, false);
122 } else {
123 xmpp_disconnect(xmppc->conn);
124 }
125 return;
126 } else if (strcmp("receive", argv[0]) == 0) {
127 if (_mam_parse_terms(xmppc, argc - 1, argv + 1, &options, true)) {
128 _mam_start_request(xmppc, &options, false, true);
129 } else {
130 xmpp_disconnect(xmppc->conn);
131 }
132 return;
133 }
134 }
135
136 _mam_usage(xmppc);
137 xmpp_disconnect(xmppc->conn);
138}
139
140static void _mam_options_init(mam_options_t *options) {
141 memset(options, 0, sizeof(*options));
142 options->max = MAM_DEFAULT_MAX;
143 options->timeout = MAM_DEFAULT_TIMEOUT_SECONDS;
144}
145
146static void _mam_options_free(mam_options_t *options) {
147 g_free(options->with);
148 g_free(options->start);
149 g_free(options->end);
150 g_free(options->after_id);
151 g_free(options->before_id);
152 g_free(options->ids);
153}
154
155static bool _mam_parse_terms(xmppc_t *xmppc, int argc, char *argv[], mam_options_t *options, bool receive) {
156 _mam_options_init(options);
157
158 for (int i = 0; i < argc; i++) {
159 char *term = argv[i];
160 char *equals = strchr(term, '=');
161
162 if (!equals) {
163 if (!_mam_set_string_option(xmppc, &options->with, "with", term, false)) {
164 _mam_options_free(options);
165 return false;
166 }
167 options->has_narrowing = true;
168 continue;
169 }
170
171 char *key = g_strndup(term, equals - term);
172 const char *value = equals + 1;
173
174 if (strcmp(key, "with") == 0) {
175 if (!_mam_set_string_option(xmppc, &options->with, key, value, false)) {
176 g_free(key);
177 _mam_options_free(options);
178 return false;
179 }
180 options->has_narrowing = true;
181 } else if (strcmp(key, "start") == 0) {
182 char *normalized = _mam_normalize_datetime(value);
183 if (!_mam_set_string_option(xmppc, &options->start, key, normalized, false)) {
184 g_free(normalized);
185 g_free(key);
186 _mam_options_free(options);
187 return false;
188 }
189 g_free(normalized);
190 options->has_narrowing = true;
191 } else if (strcmp(key, "end") == 0) {
192 char *normalized = _mam_normalize_datetime(value);
193 if (!_mam_set_string_option(xmppc, &options->end, key, normalized, false)) {
194 g_free(normalized);
195 g_free(key);
196 _mam_options_free(options);
197 return false;
198 }
199 g_free(normalized);
200 options->has_narrowing = true;
201 } else if (strcmp(key, "after-id") == 0) {
202 if (!_mam_set_string_option(xmppc, &options->after_id, key, value, false)) {
203 g_free(key);
204 _mam_options_free(options);
205 return false;
206 }
207 options->has_narrowing = true;
208 } else if (strcmp(key, "before-id") == 0) {
209 if (!_mam_set_string_option(xmppc, &options->before_id, key, value, true)) {
210 g_free(key);
211 _mam_options_free(options);
212 return false;
213 }
214 options->has_narrowing = true;
215 } else if (strcmp(key, "ids") == 0) {
216 if (!_mam_set_string_option(xmppc, &options->ids, key, value, false)) {
217 g_free(key);
218 _mam_options_free(options);
219 return false;
220 }
221 options->has_narrowing = true;
222 } else if (strcmp(key, "max") == 0) {
223 if (!_mam_parse_max(xmppc, options, value)) {
224 g_free(key);
225 _mam_options_free(options);
226 return false;
227 }
228 } else if (receive && strcmp(key, "timeout") == 0) {
229 if (!_mam_parse_timeout(xmppc, options, value)) {
230 g_free(key);
231 _mam_options_free(options);
232 return false;
233 }
234 } else {
235 logError(xmppc, "Unknown MAM field: %s\n", key);
236 g_free(key);
237 _mam_options_free(options);
238 return false;
239 }
240
241 g_free(key);
242 }
243
244 if (receive) {
245 if (!options->with) {
246 logError(xmppc, "mam receive requires a JID or with=<jid>\n");
247 _mam_options_free(options);
248 return false;
249 }
250 if (!options->after_id) {
251 logError(xmppc, "mam receive requires after-id=<mam-id>\n");
252 _mam_options_free(options);
253 return false;
254 }
255 } else if (!options->has_narrowing) {
256 logError(xmppc, "mam list requires a JID, with=, start=, end=, after-id=, before-id=, or ids=\n");
257 _mam_options_free(options);
258 return false;
259 }
260
261 if (!options->start && !options->end && !options->after_id && !options->before_id && !options->ids) {
262 char *start = _mam_normalize_datetime("-5m");
263 options->start = start;
264 }
265
266 return true;
267}
268
269static void _mam_start_request(xmppc_t *xmppc, mam_options_t *options, bool pretty, bool receive) {
270 mam_run_t *run = g_new0(mam_run_t, 1);
271 run->xmppc = xmppc;
272 run->options = *options;
273 run->receive = receive;
274 if (receive) {
275 unsigned long timeout_ms = (unsigned long)run->options.timeout * 1000;
276 run->deadline_us = g_get_monotonic_time() + ((gint64)run->options.timeout * G_USEC_PER_SEC);
277 xmpp_timed_handler_add(xmppc->conn, _mam_receive_timeout, timeout_ms, run);
278 }
279
280 if (pretty) {
281 xmpp_handler_add (xmppc->conn, _mam_display_pretty_message, NULL,"message",NULL, run);
282 } else {
283 xmpp_handler_add (xmppc->conn, _mam_display_message, NULL,"message",NULL, run);
284 }
285
286 _mam_send_request(run);
287}
288
289static void _mam_send_request(mam_run_t *run) {
290 xmppc_t *xmppc = run->xmppc;
291 xmpp_ctx_t *ctx = xmppc->ctx;
292 char max[16];
293
294 g_snprintf(max, sizeof(max), "%d", run->options.max);
295
296 char* id = xmpp_uuid_gen(ctx);
297 g_free(run->queryid);
298 run->queryid = g_strdup(id);
299
300 xmpp_stanza_t *iq = xmpp_iq_new(ctx, "set", id);
301 xmpp_stanza_t *query = xmpp_stanza_new(ctx);
302 xmpp_stanza_set_name(query, "query");
303 xmpp_stanza_set_ns(query, MAM_NS);
304 xmpp_stanza_set_attribute(query, "queryid", id);
305
306 xmpp_stanza_t* x = xmpp_stanza_new(ctx);
307 xmpp_stanza_set_name(x,"x");
308 xmpp_stanza_set_ns(x, "jabber:x:data");
309 xmpp_stanza_set_type(x,"submit");
310 xmpp_stanza_add_child(query,x);
311
312 _mam_add_field(ctx, x, "FORM_TYPE", MAM_NS, "hidden");
313
314 if (run->options.with) {
315 _mam_add_field(ctx, x, "with", run->options.with, NULL);
316 }
317 if (run->options.start) {
318 _mam_add_field(ctx, x, "start", run->options.start, NULL);
319 }
320 if (run->options.end) {
321 _mam_add_field(ctx, x, "end", run->options.end, NULL);
322 }
323 if (run->options.ids) {
324 _mam_add_ids_field(ctx, x, run->options.ids);
325 }
326
327 xmpp_stanza_t *set = xmpp_stanza_new(ctx);
328 xmpp_stanza_set_name(set, "set");
329 xmpp_stanza_set_ns(set, RSM_NS);
330 _mam_add_text_child(ctx, set, "max", max);
331 if (run->options.after_id) {
332 _mam_add_text_child(ctx, set, "after", run->options.after_id);
333 }
334 if (run->options.before_id) {
335 _mam_add_text_child(ctx, set, "before", run->options.before_id);
336 }
337 xmpp_stanza_add_child(query, set);
338
339 xmpp_stanza_add_child(iq,query);
340 xmpp_id_handler_add(xmppc->conn, _mam_finish, id, run);
341 xmpp_send(xmppc->conn,iq);
342
343 xmpp_stanza_release(iq);
344 xmpp_stanza_release(query);
345 xmpp_stanza_release(x);
346 xmpp_stanza_release(set);
347 free(id);
348}
349
350static int _mam_finish(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata){
351 mam_run_t *run = (mam_run_t *)userdata;
352 const char *type = xmpp_stanza_get_type(stanza);
353
354 if (run->done) {
355 return 0;
356 }
357
358 if (type && strcmp(type, "error") == 0) {
359 logError(run->xmppc, "MAM request failed\n");
360 _mam_disconnect_and_free(conn, run, false);
361 return 0;
362 }
363
364 xmpp_stanza_t *fin = xmpp_stanza_get_child_by_name(stanza,"fin");
365 if(fin) {
366 if (run->receive && run->messages_total == 0 && g_get_monotonic_time() < run->deadline_us) {
367 xmpp_timed_handler_add(conn, _mam_receive_poll, MAM_POLL_PERIOD_MS, run);
368 } else {
369 _mam_disconnect_and_free(conn, run, false);
370 }
371 }
372 return 0;
373}
374
375static int _mam_receive_poll(xmpp_conn_t *const conn, void *const userdata) {
376 mam_run_t *run = (mam_run_t *)userdata;
377
378 if (run->done) {
379 return 0;
380 }
381
382 if (g_get_monotonic_time() >= run->deadline_us) {
383 _mam_disconnect_and_free(conn, run, false);
384 return 0;
385 }
386
387 _mam_send_request(run);
388 return 0;
389}
390
391static int _mam_receive_timeout(xmpp_conn_t *const conn, void *const userdata) {
392 mam_run_t *run = (mam_run_t *)userdata;
393
394 if (!run->done) {
395 _mam_disconnect_and_free(conn, run, true);
396 }
397
398 return 0;
399}
400
401static void _mam_disconnect_and_free(xmpp_conn_t *conn, mam_run_t *run, bool delete_id_handler) {
402 if (run->done) {
403 return;
404 }
405
406 run->done = true;
407 xmpp_handler_delete(conn, _mam_display_message);
408 xmpp_handler_delete(conn, _mam_display_pretty_message);
409 xmpp_timed_handler_delete(conn, _mam_receive_poll);
410 xmpp_timed_handler_delete(conn, _mam_receive_timeout);
411 if (delete_id_handler && run->queryid) {
412 xmpp_id_handler_delete(conn, _mam_finish, run->queryid);
413 }
414 _mam_options_free(&run->options);
415 g_free(run->queryid);
416 g_free(run);
417 xmpp_disconnect(conn);
418}
419
420static void _mam_add_field(xmpp_ctx_t *ctx, xmpp_stanza_t *x, const char *var, const char *value, const char *type) {
421 xmpp_stanza_t* field = xmpp_stanza_new(ctx);
422 xmpp_stanza_set_name(field,"field");
423 xmpp_stanza_set_attribute(field, "var", var);
424 if (type) {
425 xmpp_stanza_set_type(field, type);
426 }
427
428 _mam_add_text_child(ctx, field, "value", value);
429 xmpp_stanza_add_child(x, field);
430 xmpp_stanza_release(field);
431}
432
433static void _mam_add_ids_field(xmpp_ctx_t *ctx, xmpp_stanza_t *x, const char *ids) {
434 xmpp_stanza_t* field = xmpp_stanza_new(ctx);
435 xmpp_stanza_set_name(field,"field");
436 xmpp_stanza_set_attribute(field, "var", "ids");
437
438 char **parts = g_strsplit(ids, ",", -1);
439 for (int i = 0; parts[i] != NULL; i++) {
440 if (parts[i][0] != '\0') {
441 _mam_add_text_child(ctx, field, "value", parts[i]);
442 }
443 }
444 g_strfreev(parts);
445
446 xmpp_stanza_add_child(x, field);
447 xmpp_stanza_release(field);
448}
449
450static void _mam_add_text_child(xmpp_ctx_t *ctx, xmpp_stanza_t *parent, const char *name, const char *text) {
451 xmpp_stanza_t *child = xmpp_stanza_new(ctx);
452 xmpp_stanza_set_name(child, name);
453
454 xmpp_stanza_t *body = xmpp_stanza_new(ctx);
455 xmpp_stanza_set_text(body, text ? text : "");
456 xmpp_stanza_add_child(child, body);
457 xmpp_stanza_release(body);
458
459 xmpp_stanza_add_child(parent, child);
460 xmpp_stanza_release(child);
461}
462
463static bool _mam_set_string_option(xmppc_t *xmppc, char **target, const char *key, const char *value, bool allow_empty) {
464 if (*target) {
465 logError(xmppc, "Duplicate MAM field: %s\n", key);
466 return false;
467 }
468 if (!allow_empty && (!value || value[0] == '\0')) {
469 logError(xmppc, "MAM field requires a value: %s\n", key);
470 return false;
471 }
472 *target = g_strdup(value ? value : "");
473 return true;
474}
475
476static bool _mam_parse_max(xmppc_t *xmppc, mam_options_t *options, const char *value) {
477 char *end = NULL;
478 gint64 parsed = g_ascii_strtoll(value, &end, 10);
479
480 if (!value || value[0] == '\0' || !end || *end != '\0' || parsed < 1) {
481 logError(xmppc, "max must be a positive integer\n");
482 return false;
483 }
484
485 if (parsed > MAM_MAX_CAP) {
486 parsed = MAM_MAX_CAP;
487 }
488
489 options->max = (int)parsed;
490 return true;
491}
492
493static bool _mam_parse_timeout(xmppc_t *xmppc, mam_options_t *options, const char *value) {
494 gint64 seconds = 0;
495
496 if (!_mam_parse_duration_seconds(value, &seconds) || seconds < 0 || seconds > G_MAXINT || (guint64)seconds > ((guint64)G_MAXULONG / 1000)) {
497 logError(xmppc, "timeout must be a duration like 30s, 5m, or 1h\n");
498 return false;
499 }
500
501 options->timeout = (int)seconds;
502 return true;
503}
504
505static bool _mam_parse_duration_seconds(const char *value, gint64 *seconds) {
506 char *end = NULL;
507 gint64 parsed = 0;
508 gint64 multiplier = 1;
509
510 if (!value || value[0] == '\0') {
511 return false;
512 }
513
514 parsed = g_ascii_strtoll(value, &end, 10);
515 if (end == value || parsed < 0) {
516 return false;
517 }
518
519 if (*end == '\0') {
520 multiplier = 1;
521 } else if (end[1] == '\0') {
522 if (*end == 's') {
523 multiplier = 1;
524 } else if (*end == 'm') {
525 multiplier = 60;
526 } else if (*end == 'h') {
527 multiplier = 60 * 60;
528 } else if (*end == 'd') {
529 multiplier = 60 * 60 * 24;
530 } else {
531 return false;
532 }
533 } else {
534 return false;
535 }
536
537 if (parsed > G_MAXINT64 / multiplier) {
538 return false;
539 }
540
541 *seconds = parsed * multiplier;
542 return true;
543}
544
545static char *_mam_normalize_datetime(const char *value) {
546 if (value && value[0] == '-' && value[1] != '\0') {
547 gint64 seconds = 0;
548 if (_mam_parse_duration_seconds(value + 1, &seconds) && seconds > 0) {
549 GDateTime *now = g_date_time_new_now_utc();
550 GDateTime *then = g_date_time_add_seconds(now, -seconds);
551 char *formatted = g_date_time_format(then, "%Y-%m-%dT%H:%M:%SZ");
552 g_date_time_unref(then);
553 g_date_time_unref(now);
554 return formatted;
555 }
556 }
557
558 return g_strdup(value);
559}
560
561static bool _mam_result_matches(mam_run_t *run, xmpp_stanza_t *result) {
562 const char *ns = xmpp_stanza_get_ns(result);
563 if (!ns || strcmp(MAM_NS, ns) != 0) {
564 return false;
565 }
566
567 const char *queryid = xmpp_stanza_get_attribute(result, "queryid");
568 return !queryid || !run->queryid || strcmp(queryid, run->queryid) == 0;
569}
570
571static int _mam_display_message(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata) {
572 mam_run_t *run = (mam_run_t *)userdata;
573 xmpp_stanza_t* result = xmpp_stanza_get_child_by_name(stanza,"result");
574
575 if (result && _mam_result_matches(run, result) && run->messages_total < run->options.max) {
576 _mam_print_compact_message(result);
577 run->messages_total++;
578 }
579 return 1;
580}
581
582static int _mam_display_pretty_message(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata)
583{
584 mam_run_t *run = (mam_run_t *)userdata;
585 xmpp_stanza_t* result = xmpp_stanza_get_child_by_name(stanza,"result");
586
587 if( result && _mam_result_matches(run, result) && run->messages_total < run->options.max ) {
588
589 xmpp_stanza_t* forwarded = xmpp_stanza_get_child_by_ns(result, FORWARD_NS);
590 if (forwarded) {
591
592 xmpp_stanza_t* message = xmpp_stanza_get_child_by_name(forwarded, "message");
593 if (message) {
594 const char* const from = xmpp_stanza_get_from(message);
595 xmpp_stanza_t* body = xmpp_stanza_get_child_by_name(message, "body");
596
597 if (body) {
598 char *text = xmpp_stanza_get_text(body);
599 printf("%s: %s\n", from ? from : "", text ? text : "");
600 free(text);
601 run->messages_total++;
602 }
603 }
604 }
605 }
606
607 return 1;
608}
609
610static void _mam_print_compact_message(xmpp_stanza_t *result) {
611 const char *mam_id = xmpp_stanza_get_attribute(result, "id");
612 const char *message_id = "";
613 const char *stamp = "";
614 const char *from = "";
615 const char *to = "";
616 char *body_text = NULL;
617
618 xmpp_stanza_t* forwarded = xmpp_stanza_get_child_by_ns(result, FORWARD_NS);
619 xmpp_stanza_t* message = forwarded ? xmpp_stanza_get_child_by_name(forwarded, "message") : NULL;
620
621 if (forwarded) {
622 xmpp_stanza_t* delay = xmpp_stanza_get_child_by_ns(forwarded, DELAY_NS);
623 if (delay && xmpp_stanza_get_attribute(delay, "stamp")) {
624 stamp = xmpp_stanza_get_attribute(delay, "stamp");
625 }
626 }
627
628 if (message) {
629 xmpp_stanza_t* body = xmpp_stanza_get_child_by_name(message, "body");
630 if (xmpp_stanza_get_id(message)) {
631 message_id = xmpp_stanza_get_id(message);
632 }
633 if (xmpp_stanza_get_from(message)) {
634 from = xmpp_stanza_get_from(message);
635 }
636 if (xmpp_stanza_get_to(message)) {
637 to = xmpp_stanza_get_to(message);
638 }
639 if (body) {
640 body_text = xmpp_stanza_get_text(body);
641 }
642 }
643
644 printf("<message id=\"");
645 _mam_print_escaped_attr(mam_id ? mam_id : "");
646 printf("\" message-id=\"");
647 _mam_print_escaped_attr(message_id);
648 printf("\" stamp=\"");
649 _mam_print_escaped_attr(stamp);
650 printf("\" from=\"");
651 _mam_print_escaped_attr(from);
652 printf("\" to=\"");
653 _mam_print_escaped_attr(to);
654 printf("\"><body>");
655 _mam_print_escaped_text(body_text ? body_text : "");
656 printf("</body></message>\n");
657 fflush(stdout);
658
659 free(body_text);
660}
661
662static void _mam_print_escaped_attr(const char *text) {
663 _mam_print_escaped(text, true);
664}
665
666static void _mam_print_escaped_text(const char *text) {
667 _mam_print_escaped(text, false);
668}
669
670static void _mam_print_escaped(const char *text, bool attribute) {
671 const unsigned char *p = (const unsigned char *)(text ? text : "");
672
673 while (*p) {
674 switch (*p) {
675 case '&':
676 printf("&");
677 break;
678 case '<':
679 printf("<");
680 break;
681 case '>':
682 printf(">");
683 break;
684 case '"':
685 if (attribute) {
686 printf(""");
687 } else {
688 putchar('"');
689 }
690 break;
691 case '\n':
692 printf(" ");
693 break;
694 case '\r':
695 printf(" ");
696 break;
697 case '\t':
698 if (attribute) {
699 printf("	");
700 } else {
701 putchar('\t');
702 }
703 break;
704 default:
705 if (*p < 0x20) {
706 printf("&#x%02X;", *p);
707 } else {
708 putchar(*p);
709 }
710 break;
711 }
712 p++;
713 }
714}
715
716static void _mam_usage(xmppc_t *xmppc) {
717 logError(xmppc, "Usage:\n");
718 logError(xmppc, " mam list <jid> [with=<jid>] [start=<time>] [end=<time>] [after-id=<id>] [before-id=<id>] [ids=<id>[,<id>...]] [max=<n>]\n");
719 logError(xmppc, " mam receive <jid> after-id=<id> [timeout=300s] [max=5]\n");
720}