1 /*
2  * form.c
3  * vim: expandtab:ts=4:sts=4:sw=4
4  *
5  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
6  *
7  * This file is part of Profanity.
8  *
9  * Profanity is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Profanity is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
21  *
22  * In addition, as a special exception, the copyright holders give permission to
23  * link the code of portions of this program with the OpenSSL library under
24  * certain conditions as described in each individual source file, and
25  * distribute linked combinations including the two.
26  *
27  * You must obey the GNU General Public License in all respects for all of the
28  * code used other than OpenSSL. If you modify file(s) with this exception, you
29  * may extend this exception to your version of the file(s), but you are not
30  * obligated to do so. If you do not wish to do so, delete this exception
31  * statement from your version. If you delete this exception statement from all
32  * source files in the program, then also delete it here.
33  *
34  */
35 
36 #include "config.h"
37 
38 #include <string.h>
39 #include <stdlib.h>
40 
41 #ifdef HAVE_LIBMESODE
42 #include <mesode.h>
43 #endif
44 
45 #ifdef HAVE_LIBSTROPHE
46 #include <strophe.h>
47 #endif
48 
49 #include <glib.h>
50 
51 #include "log.h"
52 #include "xmpp/xmpp.h"
53 #include "xmpp/stanza.h"
54 #include "xmpp/connection.h"
55 
56 static gboolean
_is_valid_form_element(xmpp_stanza_t * stanza)57 _is_valid_form_element(xmpp_stanza_t* stanza)
58 {
59     const char* name = xmpp_stanza_get_name(stanza);
60     if (g_strcmp0(name, STANZA_NAME_X) != 0) {
61         log_error("Error parsing form, root element not <x/>.");
62         return FALSE;
63     }
64 
65     const char* ns = xmpp_stanza_get_ns(stanza);
66     if (g_strcmp0(ns, STANZA_NS_DATA) != 0) {
67         log_error("Error parsing form, namespace not %s.", STANZA_NS_DATA);
68         return FALSE;
69     }
70 
71     const char* type = xmpp_stanza_get_type(stanza);
72     if ((g_strcmp0(type, "form") != 0) && (g_strcmp0(type, "submit") != 0) && (g_strcmp0(type, "cancel") != 0) && (g_strcmp0(type, "result") != 0)) {
73         log_error("Error parsing form, unknown type.");
74         return FALSE;
75     }
76 
77     return TRUE;
78 }
79 
80 static DataForm*
_form_new(void)81 _form_new(void)
82 {
83     DataForm* form = calloc(1, sizeof(DataForm));
84 
85     return form;
86 }
87 
88 static FormField*
_field_new(void)89 _field_new(void)
90 {
91     FormField* field = calloc(1, sizeof(FormField));
92 
93     return field;
94 }
95 
96 static char*
_get_property(xmpp_stanza_t * const stanza,const char * const property)97 _get_property(xmpp_stanza_t* const stanza, const char* const property)
98 {
99     char* result = NULL;
100     xmpp_ctx_t* ctx = connection_get_ctx();
101 
102     xmpp_stanza_t* child = xmpp_stanza_get_child_by_name(stanza, property);
103     if (child) {
104         char* child_text = xmpp_stanza_get_text(child);
105         if (child_text) {
106             result = strdup(child_text);
107             xmpp_free(ctx, child_text);
108         }
109     }
110 
111     return result;
112 }
113 
114 static char*
_get_attr(xmpp_stanza_t * const stanza,const char * const attr)115 _get_attr(xmpp_stanza_t* const stanza, const char* const attr)
116 {
117     const char* result = xmpp_stanza_get_attribute(stanza, attr);
118     if (result) {
119         return strdup(result);
120     } else {
121         return NULL;
122     }
123 }
124 
125 static gboolean
_is_required(xmpp_stanza_t * const stanza)126 _is_required(xmpp_stanza_t* const stanza)
127 {
128     xmpp_stanza_t* child = xmpp_stanza_get_child_by_name(stanza, "required");
129     if (child) {
130         return TRUE;
131     } else {
132         return FALSE;
133     }
134 }
135 
136 static form_field_type_t
_get_field_type(const char * const type)137 _get_field_type(const char* const type)
138 {
139     if (g_strcmp0(type, "hidden") == 0) {
140         return FIELD_HIDDEN;
141     }
142     if (g_strcmp0(type, "text-single") == 0) {
143         return FIELD_TEXT_SINGLE;
144     }
145     if (g_strcmp0(type, "text-private") == 0) {
146         return FIELD_TEXT_PRIVATE;
147     }
148     if (g_strcmp0(type, "text-multi") == 0) {
149         return FIELD_TEXT_MULTI;
150     }
151     if (g_strcmp0(type, "boolean") == 0) {
152         return FIELD_BOOLEAN;
153     }
154     if (g_strcmp0(type, "list-single") == 0) {
155         return FIELD_LIST_SINGLE;
156     }
157     if (g_strcmp0(type, "list-multi") == 0) {
158         return FIELD_LIST_MULTI;
159     }
160     if (g_strcmp0(type, "jid-single") == 0) {
161         return FIELD_JID_SINGLE;
162     }
163     if (g_strcmp0(type, "jid-multi") == 0) {
164         return FIELD_JID_MULTI;
165     }
166     if (g_strcmp0(type, "fixed") == 0) {
167         return FIELD_FIXED;
168     }
169     return FIELD_UNKNOWN;
170 }
171 
172 DataForm*
form_create(xmpp_stanza_t * const form_stanza)173 form_create(xmpp_stanza_t* const form_stanza)
174 {
175     xmpp_ctx_t* ctx = connection_get_ctx();
176 
177     if (!_is_valid_form_element(form_stanza)) {
178         return NULL;
179     }
180 
181     DataForm* form = _form_new();
182     form->type = _get_attr(form_stanza, "type");
183     form->title = _get_property(form_stanza, "title");
184     form->instructions = _get_property(form_stanza, "instructions");
185     form->var_to_tag = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
186     form->tag_to_var = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
187     form->tag_ac = autocomplete_new();
188     form->modified = FALSE;
189 
190     int tag_num = 1;
191 
192     // get fields
193     xmpp_stanza_t* form_child = xmpp_stanza_get_children(form_stanza);
194     while (form_child) {
195         const char* child_name = xmpp_stanza_get_name(form_child);
196         if (g_strcmp0(child_name, "field") == 0) {
197             xmpp_stanza_t* field_stanza = form_child;
198 
199             FormField* field = _field_new();
200             field->label = _get_attr(field_stanza, "label");
201             field->type = _get_attr(field_stanza, "type");
202             field->type_t = _get_field_type(field->type);
203             field->value_ac = autocomplete_new();
204 
205             field->var = _get_attr(field_stanza, "var");
206 
207             if (field->type_t != FIELD_HIDDEN && field->var) {
208                 GString* tag = g_string_new("");
209                 g_string_printf(tag, "field%d", tag_num++);
210                 g_hash_table_insert(form->var_to_tag, strdup(field->var), strdup(tag->str));
211                 g_hash_table_insert(form->tag_to_var, strdup(tag->str), strdup(field->var));
212                 autocomplete_add(form->tag_ac, tag->str);
213                 g_string_free(tag, TRUE);
214             }
215 
216             field->description = _get_property(field_stanza, "desc");
217             field->required = _is_required(field_stanza);
218 
219             // handle repeated field children
220             xmpp_stanza_t* field_child = xmpp_stanza_get_children(field_stanza);
221             int value_index = 1;
222             while (field_child) {
223                 child_name = xmpp_stanza_get_name(field_child);
224 
225                 // handle values
226                 if (g_strcmp0(child_name, "value") == 0) {
227                     char* value = xmpp_stanza_get_text(field_child);
228                     if (value) {
229                         field->values = g_slist_append(field->values, strdup(value));
230 
231                         if (field->type_t == FIELD_TEXT_MULTI) {
232                             GString* ac_val = g_string_new("");
233                             g_string_printf(ac_val, "val%d", value_index++);
234                             autocomplete_add(field->value_ac, ac_val->str);
235                             g_string_free(ac_val, TRUE);
236                         }
237                         if (field->type_t == FIELD_JID_MULTI) {
238                             autocomplete_add(field->value_ac, value);
239                         }
240 
241                         xmpp_free(ctx, value);
242                     }
243 
244                     // handle options
245                 } else if (g_strcmp0(child_name, "option") == 0) {
246                     FormOption* option = malloc(sizeof(FormOption));
247                     option->label = _get_attr(field_child, "label");
248                     option->value = _get_property(field_child, "value");
249 
250                     if ((field->type_t == FIELD_LIST_SINGLE) || (field->type_t == FIELD_LIST_MULTI)) {
251                         autocomplete_add(field->value_ac, option->value);
252                     }
253 
254                     field->options = g_slist_append(field->options, option);
255                 }
256 
257                 field_child = xmpp_stanza_get_next(field_child);
258             }
259 
260             form->fields = g_slist_append(form->fields, field);
261         }
262 
263         form_child = xmpp_stanza_get_next(form_child);
264     }
265 
266     return form;
267 }
268 
269 xmpp_stanza_t*
form_create_submission(DataForm * form)270 form_create_submission(DataForm* form)
271 {
272     xmpp_ctx_t* ctx = connection_get_ctx();
273 
274     xmpp_stanza_t* x = xmpp_stanza_new(ctx);
275     xmpp_stanza_set_name(x, STANZA_NAME_X);
276     xmpp_stanza_set_ns(x, STANZA_NS_DATA);
277     xmpp_stanza_set_type(x, "submit");
278 
279     GSList* curr_field = form->fields;
280     while (curr_field) {
281         FormField* field = curr_field->data;
282 
283         if (field->type_t != FIELD_FIXED) {
284             xmpp_stanza_t* field_stanza = xmpp_stanza_new(ctx);
285             xmpp_stanza_set_name(field_stanza, "field");
286             xmpp_stanza_set_attribute(field_stanza, "var", field->var);
287 
288             xmpp_stanza_t* value_stanza = NULL;
289             GSList* curr_value = NULL;
290 
291             switch (field->type_t) {
292             case FIELD_HIDDEN:
293             case FIELD_TEXT_SINGLE:
294             case FIELD_TEXT_PRIVATE:
295             case FIELD_BOOLEAN:
296             case FIELD_LIST_SINGLE:
297             case FIELD_JID_SINGLE:
298                 value_stanza = xmpp_stanza_new(ctx);
299                 xmpp_stanza_set_name(value_stanza, "value");
300                 if (field->values) {
301                     if (field->values->data) {
302                         xmpp_stanza_t* text_stanza = xmpp_stanza_new(ctx);
303                         xmpp_stanza_set_text(text_stanza, field->values->data);
304                         xmpp_stanza_add_child(value_stanza, text_stanza);
305                         xmpp_stanza_release(text_stanza);
306                     }
307                 }
308                 xmpp_stanza_add_child(field_stanza, value_stanza);
309                 xmpp_stanza_release(value_stanza);
310 
311                 break;
312 
313             case FIELD_TEXT_MULTI:
314             case FIELD_LIST_MULTI:
315             case FIELD_JID_MULTI:
316                 curr_value = field->values;
317                 while (curr_value) {
318                     char* value = curr_value->data;
319 
320                     value_stanza = xmpp_stanza_new(ctx);
321                     xmpp_stanza_set_name(value_stanza, "value");
322                     if (value) {
323                         xmpp_stanza_t* text_stanza = xmpp_stanza_new(ctx);
324                         xmpp_stanza_set_text(text_stanza, value);
325                         xmpp_stanza_add_child(value_stanza, text_stanza);
326                         xmpp_stanza_release(text_stanza);
327                     }
328 
329                     xmpp_stanza_add_child(field_stanza, value_stanza);
330                     xmpp_stanza_release(value_stanza);
331 
332                     curr_value = g_slist_next(curr_value);
333                 }
334                 break;
335             case FIELD_FIXED:
336             default:
337                 break;
338             }
339 
340             xmpp_stanza_add_child(x, field_stanza);
341             xmpp_stanza_release(field_stanza);
342         }
343 
344         curr_field = g_slist_next(curr_field);
345     }
346 
347     return x;
348 }
349 
350 static void
_free_option(FormOption * option)351 _free_option(FormOption* option)
352 {
353     if (option) {
354         free(option->label);
355         free(option->value);
356         free(option);
357     }
358 }
359 
360 static void
_free_field(FormField * field)361 _free_field(FormField* field)
362 {
363     if (field) {
364         free(field->label);
365         free(field->type);
366         free(field->var);
367         free(field->description);
368         g_slist_free_full(field->values, free);
369         g_slist_free_full(field->options, (GDestroyNotify)_free_option);
370         autocomplete_free(field->value_ac);
371         free(field);
372     }
373 }
374 
375 void
form_destroy(DataForm * form)376 form_destroy(DataForm* form)
377 {
378     if (form) {
379         free(form->type);
380         free(form->title);
381         free(form->instructions);
382         g_slist_free_full(form->fields, (GDestroyNotify)_free_field);
383         g_hash_table_destroy(form->var_to_tag);
384         g_hash_table_destroy(form->tag_to_var);
385         autocomplete_free(form->tag_ac);
386         free(form);
387     }
388 }
389 
390 static int
_field_compare_by_var(FormField * a,FormField * b)391 _field_compare_by_var(FormField* a, FormField* b)
392 {
393     return g_strcmp0(a->var, b->var);
394 }
395 
396 GSList*
form_get_non_form_type_fields_sorted(DataForm * form)397 form_get_non_form_type_fields_sorted(DataForm* form)
398 {
399     GSList* sorted = NULL;
400     GSList* curr = form->fields;
401     while (curr) {
402         FormField* field = curr->data;
403         if (g_strcmp0(field->var, "FORM_TYPE") != 0) {
404             sorted = g_slist_insert_sorted(sorted, field, (GCompareFunc)_field_compare_by_var);
405         }
406         curr = g_slist_next(curr);
407     }
408 
409     return sorted;
410 }
411 
412 GSList*
form_get_field_values_sorted(FormField * field)413 form_get_field_values_sorted(FormField* field)
414 {
415     GSList* sorted = NULL;
416     GSList* curr = field->values;
417     while (curr) {
418         char* value = curr->data;
419         if (value) {
420             sorted = g_slist_insert_sorted(sorted, value, (GCompareFunc)g_strcmp0);
421         }
422         curr = g_slist_next(curr);
423     }
424 
425     return sorted;
426 }
427 
428 char*
form_get_form_type_field(DataForm * form)429 form_get_form_type_field(DataForm* form)
430 {
431     GSList* curr = form->fields;
432     while (curr) {
433         FormField* field = curr->data;
434         if (g_strcmp0(field->var, "FORM_TYPE") == 0) {
435             return field->values->data;
436         }
437         curr = g_slist_next(curr);
438     }
439 
440     return NULL;
441 }
442 
443 gboolean
form_tag_exists(DataForm * form,const char * const tag)444 form_tag_exists(DataForm* form, const char* const tag)
445 {
446     GList* tags = g_hash_table_get_keys(form->tag_to_var);
447     GList* curr = tags;
448     while (curr) {
449         if (g_strcmp0(curr->data, tag) == 0) {
450             g_list_free(tags);
451             return TRUE;
452         }
453         curr = g_list_next(curr);
454     }
455 
456     g_list_free(tags);
457     return FALSE;
458 }
459 
460 form_field_type_t
form_get_field_type(DataForm * form,const char * const tag)461 form_get_field_type(DataForm* form, const char* const tag)
462 {
463     char* var = g_hash_table_lookup(form->tag_to_var, tag);
464     if (var) {
465         GSList* curr = form->fields;
466         while (curr) {
467             FormField* field = curr->data;
468             if (g_strcmp0(field->var, var) == 0) {
469                 return field->type_t;
470             }
471             curr = g_slist_next(curr);
472         }
473     }
474     return FIELD_UNKNOWN;
475 }
476 
477 void
form_set_value(DataForm * form,const char * const tag,char * value)478 form_set_value(DataForm* form, const char* const tag, char* value)
479 {
480     char* var = g_hash_table_lookup(form->tag_to_var, tag);
481     if (var) {
482         GSList* curr = form->fields;
483         while (curr) {
484             FormField* field = curr->data;
485             if (g_strcmp0(field->var, var) == 0) {
486                 if (g_slist_length(field->values) == 0) {
487                     field->values = g_slist_append(field->values, strdup(value));
488                     form->modified = TRUE;
489                     return;
490                 } else if (g_slist_length(field->values) == 1) {
491                     free(field->values->data);
492                     field->values->data = strdup(value);
493                     form->modified = TRUE;
494                     return;
495                 }
496             }
497             curr = g_slist_next(curr);
498         }
499     }
500 }
501 
502 void
form_add_value(DataForm * form,const char * const tag,char * value)503 form_add_value(DataForm* form, const char* const tag, char* value)
504 {
505     char* var = g_hash_table_lookup(form->tag_to_var, tag);
506     if (var) {
507         GSList* curr = form->fields;
508         while (curr) {
509             FormField* field = curr->data;
510             if (g_strcmp0(field->var, var) == 0) {
511                 field->values = g_slist_append(field->values, strdup(value));
512                 if (field->type_t == FIELD_TEXT_MULTI) {
513                     int total = g_slist_length(field->values);
514                     GString* value_index = g_string_new("");
515                     g_string_printf(value_index, "val%d", total);
516                     autocomplete_add(field->value_ac, value_index->str);
517                     g_string_free(value_index, TRUE);
518                 }
519                 form->modified = TRUE;
520                 return;
521             }
522             curr = g_slist_next(curr);
523         }
524     }
525 }
526 
527 gboolean
form_add_unique_value(DataForm * form,const char * const tag,char * value)528 form_add_unique_value(DataForm* form, const char* const tag, char* value)
529 {
530     char* var = g_hash_table_lookup(form->tag_to_var, tag);
531     if (var) {
532         GSList* curr = form->fields;
533         while (curr) {
534             FormField* field = curr->data;
535             if (g_strcmp0(field->var, var) == 0) {
536                 GSList* curr_value = field->values;
537                 while (curr_value) {
538                     if (g_strcmp0(curr_value->data, value) == 0) {
539                         return FALSE;
540                     }
541                     curr_value = g_slist_next(curr_value);
542                 }
543 
544                 field->values = g_slist_append(field->values, strdup(value));
545                 if (field->type_t == FIELD_JID_MULTI) {
546                     autocomplete_add(field->value_ac, value);
547                 }
548                 form->modified = TRUE;
549                 return TRUE;
550             }
551             curr = g_slist_next(curr);
552         }
553     }
554 
555     return FALSE;
556 }
557 
558 gboolean
form_remove_value(DataForm * form,const char * const tag,char * value)559 form_remove_value(DataForm* form, const char* const tag, char* value)
560 {
561     char* var = g_hash_table_lookup(form->tag_to_var, tag);
562     if (var) {
563         GSList* curr = form->fields;
564         while (curr) {
565             FormField* field = curr->data;
566             if (g_strcmp0(field->var, var) == 0) {
567                 GSList* found = g_slist_find_custom(field->values, value, (GCompareFunc)g_strcmp0);
568                 if (found) {
569                     free(found->data);
570                     found->data = NULL;
571                     field->values = g_slist_delete_link(field->values, found);
572                     if (field->type_t == FIELD_JID_MULTI) {
573                         autocomplete_remove(field->value_ac, value);
574                     }
575                     form->modified = TRUE;
576                     return TRUE;
577                 } else {
578                     return FALSE;
579                 }
580             }
581             curr = g_slist_next(curr);
582         }
583     }
584 
585     return FALSE;
586 }
587 
588 gboolean
form_remove_text_multi_value(DataForm * form,const char * const tag,int index)589 form_remove_text_multi_value(DataForm* form, const char* const tag, int index)
590 {
591     index--;
592     char* var = g_hash_table_lookup(form->tag_to_var, tag);
593     if (var) {
594         GSList* curr = form->fields;
595         while (curr) {
596             FormField* field = curr->data;
597             if (g_strcmp0(field->var, var) == 0) {
598                 GSList* item = g_slist_nth(field->values, index);
599                 if (item) {
600                     free(item->data);
601                     item->data = NULL;
602                     field->values = g_slist_delete_link(field->values, item);
603                     GString* value_index = g_string_new("");
604                     g_string_printf(value_index, "val%d", index + 1);
605                     autocomplete_remove(field->value_ac, value_index->str);
606                     g_string_free(value_index, TRUE);
607                     form->modified = TRUE;
608                     return TRUE;
609                 } else {
610                     return FALSE;
611                 }
612             }
613             curr = g_slist_next(curr);
614         }
615     }
616 
617     return FALSE;
618 }
619 
620 int
form_get_value_count(DataForm * form,const char * const tag)621 form_get_value_count(DataForm* form, const char* const tag)
622 {
623     char* var = g_hash_table_lookup(form->tag_to_var, tag);
624     if (var) {
625         GSList* curr = form->fields;
626         while (curr) {
627             FormField* field = curr->data;
628             if (g_strcmp0(field->var, var) == 0) {
629                 if ((g_slist_length(field->values) == 1) && (field->values->data == NULL)) {
630                     return 0;
631                 } else {
632                     return g_slist_length(field->values);
633                 }
634             }
635             curr = g_slist_next(curr);
636         }
637     }
638 
639     return 0;
640 }
641 
642 gboolean
form_field_contains_option(DataForm * form,const char * const tag,char * value)643 form_field_contains_option(DataForm* form, const char* const tag, char* value)
644 {
645     char* var = g_hash_table_lookup(form->tag_to_var, tag);
646     if (var) {
647         GSList* curr = form->fields;
648         while (curr) {
649             FormField* field = curr->data;
650             if (g_strcmp0(field->var, var) == 0) {
651                 GSList* curr_option = field->options;
652                 while (curr_option) {
653                     FormOption* option = curr_option->data;
654                     if (g_strcmp0(option->value, value) == 0) {
655                         return TRUE;
656                     }
657                     curr_option = g_slist_next(curr_option);
658                 }
659             }
660             curr = g_slist_next(curr);
661         }
662     }
663 
664     return FALSE;
665 }
666 
667 FormField*
form_get_field_by_tag(DataForm * form,const char * const tag)668 form_get_field_by_tag(DataForm* form, const char* const tag)
669 {
670     char* var = g_hash_table_lookup(form->tag_to_var, tag);
671     if (var) {
672         GSList* curr = form->fields;
673         while (curr) {
674             FormField* field = curr->data;
675             if (g_strcmp0(field->var, var) == 0) {
676                 return field;
677             }
678             curr = g_slist_next(curr);
679         }
680     }
681     return NULL;
682 }
683 
684 Autocomplete
form_get_value_ac(DataForm * form,const char * const tag)685 form_get_value_ac(DataForm* form, const char* const tag)
686 {
687     char* var = g_hash_table_lookup(form->tag_to_var, tag);
688     if (var) {
689         GSList* curr = form->fields;
690         while (curr) {
691             FormField* field = curr->data;
692             if (g_strcmp0(field->var, var) == 0) {
693                 return field->value_ac;
694             }
695             curr = g_slist_next(curr);
696         }
697     }
698     return NULL;
699 }
700 
701 void
form_reset_autocompleters(DataForm * form)702 form_reset_autocompleters(DataForm* form)
703 {
704     autocomplete_reset(form->tag_ac);
705     GSList* curr_field = form->fields;
706     while (curr_field) {
707         FormField* field = curr_field->data;
708         autocomplete_reset(field->value_ac);
709         curr_field = g_slist_next(curr_field);
710     }
711 }
712