Coverage Report

Created: 2025-12-31 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/protocols/jabber/xdata.c
Line
Count
Source
1
/*
2
 * purple - Jabber Protocol Plugin
3
 *
4
 * Purple is the legal property of its developers, whose names are too numerous
5
 * to list here.  Please refer to the COPYRIGHT file distributed with this
6
 * source distribution.
7
 *
8
 * This program is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation; either version 2 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21
 *
22
 */
23
#include "internal.h"
24
#include "request.h"
25
#include "server.h"
26
27
#include "xdata.h"
28
29
typedef enum {
30
  JABBER_X_DATA_IGNORE = 0,
31
  JABBER_X_DATA_TEXT_SINGLE,
32
  JABBER_X_DATA_TEXT_MULTI,
33
  JABBER_X_DATA_LIST_SINGLE,
34
  JABBER_X_DATA_LIST_MULTI,
35
  JABBER_X_DATA_BOOLEAN,
36
  JABBER_X_DATA_JID_SINGLE
37
} jabber_x_data_field_type;
38
39
struct jabber_x_data_data {
40
  GHashTable *fields;
41
  GSList *values;
42
  jabber_x_data_action_cb cb;
43
  gpointer user_data;
44
  JabberStream *js;
45
  GList *actions;
46
  PurpleRequestFieldGroup *actiongroup;
47
};
48
49
0
static void jabber_x_data_ok_cb(struct jabber_x_data_data *data, PurpleRequestFields *fields) {
50
0
  xmlnode *result = xmlnode_new("x");
51
0
  jabber_x_data_action_cb cb = data->cb;
52
0
  gpointer user_data = data->user_data;
53
0
  JabberStream *js = data->js;
54
0
  GList *groups, *flds;
55
0
  char *actionhandle = NULL;
56
0
  gboolean hasActions = (data->actions != NULL);
57
58
0
  xmlnode_set_namespace(result, "jabber:x:data");
59
0
  xmlnode_set_attrib(result, "type", "submit");
60
61
0
  for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) {
62
0
    if(groups->data == data->actiongroup) {
63
0
      for(flds = purple_request_field_group_get_fields(groups->data); flds; flds = flds->next) {
64
0
        PurpleRequestField *field = flds->data;
65
0
        const char *id = purple_request_field_get_id(field);
66
0
        int handleindex;
67
0
        if(!purple_strequal(id, "libpurple:jabber:xdata:actions"))
68
0
          continue;
69
0
        handleindex = purple_request_field_choice_get_value(field);
70
0
        actionhandle = g_strdup(g_list_nth_data(data->actions, handleindex));
71
0
        break;
72
0
      }
73
0
      continue;
74
0
    }
75
0
    for(flds = purple_request_field_group_get_fields(groups->data); flds; flds = flds->next) {
76
0
      xmlnode *fieldnode, *valuenode;
77
0
      PurpleRequestField *field = flds->data;
78
0
      const char *id = purple_request_field_get_id(field);
79
0
      jabber_x_data_field_type type = GPOINTER_TO_INT(g_hash_table_lookup(data->fields, id));
80
81
0
      switch(type) {
82
0
        case JABBER_X_DATA_TEXT_SINGLE:
83
0
        case JABBER_X_DATA_JID_SINGLE:
84
0
          {
85
0
          const char *value = purple_request_field_string_get_value(field);
86
0
          if (value == NULL)
87
0
            break;
88
0
          fieldnode = xmlnode_new_child(result, "field");
89
0
          xmlnode_set_attrib(fieldnode, "var", id);
90
0
          valuenode = xmlnode_new_child(fieldnode, "value");
91
0
          if(value)
92
0
            xmlnode_insert_data(valuenode, value, -1);
93
0
          break;
94
0
          }
95
0
        case JABBER_X_DATA_TEXT_MULTI:
96
0
          {
97
0
          char **pieces, **p;
98
0
          const char *value = purple_request_field_string_get_value(field);
99
0
          if (value == NULL)
100
0
            break;
101
0
          fieldnode = xmlnode_new_child(result, "field");
102
0
          xmlnode_set_attrib(fieldnode, "var", id);
103
104
0
          pieces = g_strsplit(value, "\n", -1);
105
0
          for(p = pieces; *p != NULL; p++) {
106
0
            valuenode = xmlnode_new_child(fieldnode, "value");
107
0
            xmlnode_insert_data(valuenode, *p, -1);
108
0
          }
109
0
          g_strfreev(pieces);
110
0
          }
111
0
          break;
112
0
        case JABBER_X_DATA_LIST_SINGLE:
113
0
        case JABBER_X_DATA_LIST_MULTI:
114
0
          {
115
0
          GList *selected = purple_request_field_list_get_selected(field);
116
0
          char *value;
117
0
          fieldnode = xmlnode_new_child(result, "field");
118
0
          xmlnode_set_attrib(fieldnode, "var", id);
119
120
0
          while(selected) {
121
0
            value = purple_request_field_list_get_data(field, selected->data);
122
0
            valuenode = xmlnode_new_child(fieldnode, "value");
123
0
            if(value)
124
0
              xmlnode_insert_data(valuenode, value, -1);
125
0
            selected = selected->next;
126
0
          }
127
0
          }
128
0
          break;
129
0
        case JABBER_X_DATA_BOOLEAN:
130
0
          fieldnode = xmlnode_new_child(result, "field");
131
0
          xmlnode_set_attrib(fieldnode, "var", id);
132
0
          valuenode = xmlnode_new_child(fieldnode, "value");
133
0
          if(purple_request_field_bool_get_value(field))
134
0
            xmlnode_insert_data(valuenode, "1", -1);
135
0
          else
136
0
            xmlnode_insert_data(valuenode, "0", -1);
137
0
          break;
138
0
        case JABBER_X_DATA_IGNORE:
139
0
          break;
140
0
      }
141
0
    }
142
0
  }
143
144
0
  g_hash_table_destroy(data->fields);
145
0
  while(data->values) {
146
0
    g_free(data->values->data);
147
0
    data->values = g_slist_delete_link(data->values, data->values);
148
0
  }
149
0
  if (data->actions) {
150
0
    GList *action;
151
0
    for(action = data->actions; action; action = g_list_next(action)) {
152
0
      g_free(action->data);
153
0
    }
154
0
    g_list_free(data->actions);
155
0
  }
156
0
  g_free(data);
157
158
0
  if (hasActions)
159
0
    cb(js, result, actionhandle, user_data);
160
0
  else
161
0
    ((jabber_x_data_cb)cb)(js, result, user_data);
162
163
0
  g_free(actionhandle);
164
0
}
165
166
0
static void jabber_x_data_cancel_cb(struct jabber_x_data_data *data, PurpleRequestFields *fields) {
167
0
  xmlnode *result = xmlnode_new("x");
168
0
  jabber_x_data_action_cb cb = data->cb;
169
0
  gpointer user_data = data->user_data;
170
0
  JabberStream *js = data->js;
171
0
  gboolean hasActions = FALSE;
172
0
  g_hash_table_destroy(data->fields);
173
0
  while(data->values) {
174
0
    g_free(data->values->data);
175
0
    data->values = g_slist_delete_link(data->values, data->values);
176
0
  }
177
0
  if (data->actions) {
178
0
    GList *action;
179
0
    hasActions = TRUE;
180
0
    for(action = data->actions; action; action = g_list_next(action)) {
181
0
      g_free(action->data);
182
0
    }
183
0
    g_list_free(data->actions);
184
0
  }
185
0
  g_free(data);
186
187
0
  xmlnode_set_namespace(result, "jabber:x:data");
188
0
  xmlnode_set_attrib(result, "type", "cancel");
189
190
0
  if (hasActions)
191
0
    cb(js, result, NULL, user_data);
192
0
  else
193
0
    ((jabber_x_data_cb)cb)(js, result, user_data);
194
0
}
195
196
void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data)
197
0
{
198
0
  return jabber_x_data_request_with_actions(js, packet, NULL, 0, (jabber_x_data_action_cb)cb, user_data);
199
0
}
200
201
void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data)
202
0
{
203
0
  void *handle;
204
0
  xmlnode *fn, *x;
205
0
  PurpleRequestFields *fields;
206
0
  PurpleRequestFieldGroup *group;
207
0
  PurpleRequestField *field = NULL;
208
209
0
  char *title = NULL;
210
0
  char *instructions = NULL;
211
212
0
  struct jabber_x_data_data *data = g_new0(struct jabber_x_data_data, 1);
213
214
0
  data->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
215
0
  data->user_data = user_data;
216
0
  data->cb = cb;
217
0
  data->js = js;
218
219
0
  fields = purple_request_fields_new();
220
0
  group = purple_request_field_group_new(NULL);
221
0
  purple_request_fields_add_group(fields, group);
222
223
0
  for(fn = xmlnode_get_child(packet, "field"); fn; fn = xmlnode_get_next_twin(fn)) {
224
0
    xmlnode *valuenode;
225
0
    const char *type = xmlnode_get_attrib(fn, "type");
226
0
    const char *label = xmlnode_get_attrib(fn, "label");
227
0
    const char *var = xmlnode_get_attrib(fn, "var");
228
0
    char *value = NULL;
229
230
0
    if(!type)
231
0
      type = "text-single";
232
233
0
    if(!var && !purple_strequal(type, "fixed"))
234
0
      continue;
235
0
    if(!label)
236
0
      label = var;
237
238
0
    if(purple_strequal(type, "text-private")) {
239
0
      if((valuenode = xmlnode_get_child(fn, "value")))
240
0
        value = xmlnode_get_data(valuenode);
241
242
0
      field = purple_request_field_string_new(var, label,
243
0
          value ? value : "", FALSE);
244
0
      purple_request_field_string_set_masked(field, TRUE);
245
0
      purple_request_field_group_add_field(group, field);
246
247
0
      g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_TEXT_SINGLE));
248
249
0
      g_free(value);
250
0
    } else if(purple_strequal(type, "text-multi") || purple_strequal(type, "jid-multi")) {
251
0
      GString *str = g_string_new("");
252
253
0
      for(valuenode = xmlnode_get_child(fn, "value"); valuenode;
254
0
          valuenode = xmlnode_get_next_twin(valuenode)) {
255
256
0
        if(!(value = xmlnode_get_data(valuenode)))
257
0
          continue;
258
259
0
        g_string_append_printf(str, "%s\n", value);
260
0
        g_free(value);
261
0
      }
262
263
0
      field = purple_request_field_string_new(var, label,
264
0
          str->str, TRUE);
265
0
      purple_request_field_group_add_field(group, field);
266
267
0
      g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_TEXT_MULTI));
268
269
0
      g_string_free(str, TRUE);
270
0
    } else if(purple_strequal(type, "list-single") || purple_strequal(type, "list-multi")) {
271
0
      xmlnode *optnode;
272
0
      GList *selected = NULL;
273
274
0
      field = purple_request_field_list_new(var, label);
275
276
0
      if(purple_strequal(type, "list-multi")) {
277
0
        purple_request_field_list_set_multi_select(field, TRUE);
278
0
        g_hash_table_replace(data->fields, g_strdup(var),
279
0
            GINT_TO_POINTER(JABBER_X_DATA_LIST_MULTI));
280
0
      } else {
281
0
        g_hash_table_replace(data->fields, g_strdup(var),
282
0
            GINT_TO_POINTER(JABBER_X_DATA_LIST_SINGLE));
283
0
      }
284
285
0
      for(valuenode = xmlnode_get_child(fn, "value"); valuenode;
286
0
          valuenode = xmlnode_get_next_twin(valuenode)) {
287
0
        char *data = xmlnode_get_data(valuenode);
288
0
        if (data != NULL) {
289
0
          selected = g_list_prepend(selected, data);
290
0
        }
291
0
      }
292
293
0
      for(optnode = xmlnode_get_child(fn, "option"); optnode;
294
0
          optnode = xmlnode_get_next_twin(optnode)) {
295
0
        const char *lbl;
296
297
0
        if(!(valuenode = xmlnode_get_child(optnode, "value")))
298
0
          continue;
299
300
0
        if(!(value = xmlnode_get_data(valuenode)))
301
0
          continue;
302
303
0
        if(!(lbl = xmlnode_get_attrib(optnode, "label")))
304
0
          lbl = value;
305
306
0
        data->values = g_slist_prepend(data->values, value);
307
308
0
        purple_request_field_list_add_icon(field, lbl, NULL, value);
309
0
        if(g_list_find_custom(selected, value, (GCompareFunc)strcmp))
310
0
          purple_request_field_list_add_selected(field, lbl);
311
0
      }
312
0
      purple_request_field_group_add_field(group, field);
313
314
0
      while(selected) {
315
0
        g_free(selected->data);
316
0
        selected = g_list_delete_link(selected, selected);
317
0
      }
318
319
0
    } else if(purple_strequal(type, "boolean")) {
320
0
      gboolean def = FALSE;
321
322
0
      if((valuenode = xmlnode_get_child(fn, "value")))
323
0
        value = xmlnode_get_data(valuenode);
324
325
0
      if(value && (!g_ascii_strcasecmp(value, "yes") ||
326
0
            !g_ascii_strcasecmp(value, "true") || !g_ascii_strcasecmp(value, "1")))
327
0
        def = TRUE;
328
329
0
      field = purple_request_field_bool_new(var, label, def);
330
0
      purple_request_field_group_add_field(group, field);
331
332
0
      g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_BOOLEAN));
333
334
0
      g_free(value);
335
0
    } else if(purple_strequal(type, "fixed")) {
336
0
      if((valuenode = xmlnode_get_child(fn, "value")))
337
0
        value = xmlnode_get_data(valuenode);
338
339
0
      if(value != NULL) {
340
0
        field = purple_request_field_label_new("", value);
341
0
        purple_request_field_group_add_field(group, field);
342
343
0
        g_free(value);
344
0
      }
345
0
    } else if(purple_strequal(type, "hidden")) {
346
0
      if((valuenode = xmlnode_get_child(fn, "value")))
347
0
        value = xmlnode_get_data(valuenode);
348
349
0
      field = purple_request_field_string_new(var, "", value ? value : "",
350
0
          FALSE);
351
0
      purple_request_field_set_visible(field, FALSE);
352
0
      purple_request_field_group_add_field(group, field);
353
354
0
      g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_TEXT_SINGLE));
355
356
0
      g_free(value);
357
0
    } else { /* text-single, jid-single, and the default */
358
0
      if((valuenode = xmlnode_get_child(fn, "value")))
359
0
        value = xmlnode_get_data(valuenode);
360
361
0
      field = purple_request_field_string_new(var, label,
362
0
          value ? value : "", FALSE);
363
0
      purple_request_field_group_add_field(group, field);
364
365
0
      if(purple_strequal(type, "jid-single")) {
366
0
        purple_request_field_set_type_hint(field, "screenname");
367
0
        g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_JID_SINGLE));
368
0
      } else {
369
0
        g_hash_table_replace(data->fields, g_strdup(var), GINT_TO_POINTER(JABBER_X_DATA_TEXT_SINGLE));
370
0
      }
371
372
0
      g_free(value);
373
0
    }
374
375
0
    if(field && xmlnode_get_child(fn, "required"))
376
0
      purple_request_field_set_required(field,TRUE);
377
0
  }
378
379
0
  if(actions != NULL) {
380
0
    PurpleRequestField *actionfield;
381
0
    GList *action;
382
0
    data->actiongroup = group = purple_request_field_group_new(_("Actions"));
383
0
    purple_request_fields_add_group(fields, group);
384
0
    actionfield = purple_request_field_choice_new("libpurple:jabber:xdata:actions", _("Select an action"), defaultaction);
385
386
0
    for(action = actions; action; action = g_list_next(action)) {
387
0
      JabberXDataAction *a = action->data;
388
389
0
      purple_request_field_choice_add(actionfield, a->name);
390
0
      data->actions = g_list_append(data->actions, g_strdup(a->handle));
391
0
    }
392
0
    purple_request_field_set_required(actionfield,TRUE);
393
0
    purple_request_field_group_add_field(group, actionfield);
394
0
  }
395
396
0
  if((x = xmlnode_get_child(packet, "title")))
397
0
    title = xmlnode_get_data(x);
398
399
0
  if((x = xmlnode_get_child(packet, "instructions")))
400
0
    instructions = xmlnode_get_data(x);
401
402
0
  handle = purple_request_fields(js->gc, title, title, instructions, fields,
403
0
      _("OK"), G_CALLBACK(jabber_x_data_ok_cb),
404
0
      _("Cancel"), G_CALLBACK(jabber_x_data_cancel_cb),
405
0
      purple_connection_get_account(js->gc), /* XXX Do we have a who here? */ NULL, NULL,
406
0
      data);
407
408
0
  g_free(title);
409
0
  g_free(instructions);
410
411
0
  return handle;
412
0
}
413
414
gchar *
415
jabber_x_data_get_formtype(const xmlnode *form)
416
0
{
417
0
  xmlnode *field;
418
419
0
  g_return_val_if_fail(form != NULL, NULL);
420
421
0
  for (field = xmlnode_get_child((xmlnode *)form, "field"); field;
422
0
      field = xmlnode_get_next_twin(field)) {
423
0
    const char *var = xmlnode_get_attrib(field, "var");
424
0
    if (purple_strequal(var, "FORM_TYPE")) {
425
0
      xmlnode *value = xmlnode_get_child(field, "value");
426
0
      if (value)
427
0
        return xmlnode_get_data(value);
428
0
      else
429
        /* An interesting corner case... Looking for a second
430
         * FORM_TYPE would be more considerate, but I'm in favor
431
         * of not helping broken clients.
432
         */
433
0
        return NULL;
434
0
    }
435
0
  }
436
437
  /* Erm, none found :( */
438
0
  return NULL;
439
0
}
440