1 /*
2 * search-channel.c - implementation of ContactSearch channels
3 * Copyright (C) 2009 Collabora Ltd.
4 * Copyright (C) 2009 Nokia Corporation
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "config.h"
22 #include "search-channel.h"
23
24 #include <string.h>
25
26 #include <telepathy-glib/telepathy-glib.h>
27 #include <telepathy-glib/telepathy-glib-dbus.h>
28
29 #include <wocky/wocky.h>
30
31 #define DEBUG_FLAG GABBLE_DEBUG_SEARCH
32 #include <gabble/error.h>
33 #include "connection.h"
34 #include "debug.h"
35 #include "gabble-signals-marshal.h"
36 #include "namespaces.h"
37 #include "util.h"
38
39 /* properties */
40 enum
41 {
42 PROP_SEARCH_STATE = 1,
43 PROP_AVAILABLE_SEARCH_KEYS,
44 PROP_SERVER,
45 PROP_LIMIT,
46 LAST_PROPERTY
47 };
48
49 /* signal enum */
50 enum
51 {
52 READY_OR_NOT,
53 LAST_SIGNAL
54 };
55
56 static guint signals[LAST_SIGNAL] = {0};
57
58 /* private structure */
59 struct _GabbleSearchChannelPrivate
60 {
61 TpChannelContactSearchState state;
62 gchar **available_search_keys;
63 gchar *server;
64
65 gboolean xforms;
66
67 /* owned tp_name (gchar *) => owned xmpp_name (gchar *)
68 * This mapping contains the fields that are supported by the server so
69 * if a tp_name can be mapped to different xmpp_name, the hash table will
70 * map to the one supported. */
71 GHashTable *tp_to_xmpp;
72
73 /* Array of owned (gchar *) containing all the boolean search terms
74 * supported by this server. */
75 GPtrArray *boolean_keys;
76
77 GHashTable *results;
78
79 /* TRUE if the channel is ready to be used (we received the keys supported
80 * by the server). */
81 gboolean ready;
82
83 TpHandleSet *result_handles;
84 };
85
86 /* Human-readable values of TpChannelContactSearchState. */
87 static const gchar *states[] = {
88 "not started",
89 "in progress",
90 "more available",
91 "completed",
92 "failed",
93 };
94
95 static void contact_search_iface_init (gpointer, gpointer);
96
97 G_DEFINE_TYPE_WITH_CODE (GabbleSearchChannel, gabble_search_channel,
98 TP_TYPE_BASE_CHANNEL,
99 G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_CONTACT_SEARCH,
100 contact_search_iface_init);
101 )
102
103 /* Mapping between XEP 0055/misc data forms fields seen in the wild and
104 * vCard/Telepathy names.
105 */
106
107 typedef struct {
108 gchar *xmpp_name;
109 gchar *tp_name;
110 } FieldNameMapping;
111
112 /* XMPP is not clear about the semantic of "first" and "last". We asked for
113 * clarifications on the standards mailing list and got confirmation
114 * that "first" is actually "Given Name" and "last" is "Family Name".
115 * See http://mail.jabber.org/pipermail/standards/2009-August/022530.html */
116
117 static const FieldNameMapping field_mappings[] = {
118 /* Fields specified for non-Data Forms searches */
119 { "first", "x-n-given" },
120 { "last", "x-n-family" },
121 { "nick", "nickname" },
122 { "email", "email" },
123 /* Fields observed in implementations of Data Forms searches */
124 /* ejabberd */
125 { "user", "x-telepathy-identifier" },
126 { "fn", "fn" },
127 { "middle", "x-n-additional" },
128 { "bday", "bday" },
129 { "ctry", "x-adr-country" },
130 { "locality", "x-adr-locality" },
131 { "x-gender", "x-gender" },
132 { "orgname", "x-org-name" },
133 { "orgunit", "x-org-unit" },
134 { "given", "x-n-given" },
135 { "family", "x-n-family" },
136 { "nickname", "nickname" },
137 /* openfire */
138 { "search", "" }, /* one big search box */
139 { "Name", "fn" },
140 { "Email", "email" },
141 /* openfire also includes "Username" which is the user part of the jid */
142 { NULL, NULL },
143 };
144
145 #define NUM_UNEXTENDED_FIELDS 4
146
147 static GHashTable *xmpp_to_tp = NULL;
148 static GHashTable *unextended_xmpp_to_tp = NULL;
149
150 static void
build_mapping_tables(void)151 build_mapping_tables (void)
152 {
153 guint i;
154
155 g_return_if_fail (xmpp_to_tp == NULL);
156
157 xmpp_to_tp = g_hash_table_new (g_str_hash, g_str_equal);
158 unextended_xmpp_to_tp = g_hash_table_new (g_str_hash, g_str_equal);
159
160 for (i = 0; i < NUM_UNEXTENDED_FIELDS; i++)
161 {
162 g_hash_table_insert (xmpp_to_tp, field_mappings[i].xmpp_name,
163 field_mappings[i].tp_name);
164 }
165
166 tp_g_hash_table_update (unextended_xmpp_to_tp, xmpp_to_tp, NULL, NULL);
167
168 for (; field_mappings[i].xmpp_name != NULL; i++)
169 {
170 g_hash_table_insert (xmpp_to_tp, field_mappings[i].xmpp_name,
171 field_mappings[i].tp_name);
172 }
173 }
174
175 /* Supported field */
176
177 static void
supported_fields_discovered(GabbleSearchChannel * chan)178 supported_fields_discovered (GabbleSearchChannel *chan)
179 {
180 DEBUG ("called");
181
182 tp_base_channel_register ((TpBaseChannel *) chan);
183 chan->priv->ready = TRUE;
184 g_signal_emit (chan, signals[READY_OR_NOT], 0, 0, 0, NULL);
185 }
186
187 static void
supported_field_discovery_failed(GabbleSearchChannel * chan,const GError * error)188 supported_field_discovery_failed (GabbleSearchChannel *chan,
189 const GError *error)
190 {
191 DEBUG ("called: %s, %u, %s", g_quark_to_string (error->domain), error->code,
192 error->message);
193
194 g_signal_emit (chan, signals[READY_OR_NOT], 0, error->domain, error->code,
195 error->message);
196 }
197
198 static GPtrArray *
parse_unextended_field_response(GabbleSearchChannel * self,WockyNode * query_node,GError ** error)199 parse_unextended_field_response (
200 GabbleSearchChannel *self,
201 WockyNode *query_node,
202 GError **error)
203 {
204 GPtrArray *search_keys = g_ptr_array_new ();
205 WockyNodeIter i;
206 WockyNode *field;
207
208 wocky_node_iter_init (&i, query_node, NULL, NULL);
209 while (wocky_node_iter_next (&i, &field))
210 {
211 gchar *tp_name;
212
213 if (!strcmp (field->name, "instructions"))
214 {
215 DEBUG ("server gave us some instructions: %s",
216 field->content);
217 continue;
218 }
219
220 tp_name = g_hash_table_lookup (unextended_xmpp_to_tp, field->name);
221
222 if (tp_name != NULL)
223 {
224 g_ptr_array_add (search_keys, tp_name);
225 g_hash_table_insert (self->priv->tp_to_xmpp, g_strdup (tp_name),
226 g_strdup (field->name));
227 }
228 else
229 {
230 g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
231 "server is broken: %s is not a field defined in XEP 0055",
232 field->name);
233 g_ptr_array_unref (search_keys);
234 return NULL;
235 }
236 }
237
238 return search_keys;
239 }
240
241 static gboolean
name_in_array(GPtrArray * array,const gchar * name)242 name_in_array (GPtrArray *array,
243 const gchar *name)
244 {
245 guint i;
246
247 for (i = 0; i < array->len; i++)
248 {
249 if (!tp_strdiff (g_ptr_array_index (array, i), name))
250 return TRUE;
251 }
252
253 return FALSE;
254 }
255
256 static GPtrArray *
parse_data_form(GabbleSearchChannel * self,WockyNode * x_node,GError ** error)257 parse_data_form (
258 GabbleSearchChannel *self,
259 WockyNode *x_node,
260 GError **error)
261 {
262 GPtrArray *search_keys = g_ptr_array_new ();
263 WockyNodeIter i;
264 WockyNode *n;
265
266 if (tp_strdiff (wocky_node_get_attribute (x_node, "type"), "form"))
267 {
268 g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
269 "server is broken: <x> not type='form'");
270 goto fail;
271 }
272
273 wocky_node_iter_init (&i, x_node, NULL, NULL);
274 while (wocky_node_iter_next (&i, &n))
275 {
276 const gchar *type = wocky_node_get_attribute (n, "type");
277 const gchar *var = wocky_node_get_attribute (n, "var");
278 gchar *tp_name;
279
280 if (!strcmp (n->name, "title") ||
281 !strcmp (n->name, "instructions"))
282 {
283 DEBUG ("ignoring <%s>: %s", n->name, n->content);
284 continue;
285 }
286
287 if (strcmp (n->name, "field"))
288 {
289 /* <reported> and <item> don't make sense here, and nothing else is
290 * legal.
291 */
292 DEBUG ("<%s> is not <title>, <instructions> or <field>", n->name);
293 continue;
294 }
295
296 if (!strcmp (var, "FORM_TYPE"))
297 {
298 const gchar *form_type = wocky_node_get_content_from_child (n,
299 "value");
300
301 if (tp_strdiff (form_type, NS_SEARCH))
302 {
303 DEBUG ("<x> form does not have FORM_TYPE %s", NS_SEARCH);
304 g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
305 "server is broken: form lacking FORM_TYPE %s", NS_SEARCH);
306 goto fail;
307 }
308
309 continue;
310 }
311
312 /* Openfire's search plugin has one search box, called "search", and
313 * tickyboxes controlling which fields it searches.
314 *
315 * So: if the only non-tickybox is a field called "search", expose that
316 * field as "", and remember the tickyboxes. When submitting the form,
317 * tick them all (XXX: or maybe have a whitelist?)
318 */
319 if (!tp_strdiff (type, "boolean"))
320 {
321 g_ptr_array_add (self->priv->boolean_keys, g_strdup (var));
322 continue;
323 }
324
325 tp_name = g_hash_table_lookup (xmpp_to_tp, var);
326 if (tp_name != NULL)
327 {
328 if (name_in_array (search_keys, tp_name))
329 {
330 DEBUG ("%s has already been added as a search key; Skipping",
331 tp_name);
332 }
333 else
334 {
335 g_ptr_array_add (search_keys, tp_name);
336 g_hash_table_insert (self->priv->tp_to_xmpp, g_strdup (tp_name),
337 g_strdup (var));
338 }
339 }
340 else
341 {
342 DEBUG ("Unknown data form field: %s\n", var);
343 }
344 }
345
346 return search_keys;
347
348 fail:
349 g_ptr_array_unref (search_keys);
350 return NULL;
351 }
352
353 static void
parse_search_field_response(GabbleSearchChannel * chan,WockyNode * query_node)354 parse_search_field_response (GabbleSearchChannel *chan,
355 WockyNode *query_node)
356 {
357 WockyNode *x_node;
358 GPtrArray *search_keys = NULL;
359 GError *e = NULL;
360
361 g_return_if_fail (query_node != NULL);
362
363 x_node = wocky_node_get_child_ns (query_node, "x",
364 NS_X_DATA);
365
366 if (x_node == NULL)
367 {
368 chan->priv->xforms = FALSE;
369 search_keys = parse_unextended_field_response (chan, query_node, &e);
370 }
371 else
372 {
373 chan->priv->xforms = TRUE;
374 search_keys = parse_data_form (chan, x_node, &e);
375 }
376
377 if (search_keys == NULL)
378 {
379 supported_field_discovery_failed (chan, e);
380 g_error_free (e);
381 return;
382 }
383
384 DEBUG ("extracted available fields");
385 g_ptr_array_add (search_keys, NULL);
386 chan->priv->available_search_keys = (gchar **) g_ptr_array_free (search_keys,
387 FALSE);
388
389 supported_fields_discovered (chan);
390 }
391
392 static void
query_reply_cb(GabbleConnection * conn,WockyStanza * sent_msg,WockyStanza * reply_msg,GObject * object,gpointer user_data)393 query_reply_cb (GabbleConnection *conn,
394 WockyStanza *sent_msg,
395 WockyStanza *reply_msg,
396 GObject *object,
397 gpointer user_data)
398 {
399 GabbleSearchChannel *chan = GABBLE_SEARCH_CHANNEL (object);
400 WockyNode *query_node;
401 GError *err = NULL;
402
403 query_node = wocky_node_get_child_ns (
404 wocky_stanza_get_top_node (reply_msg), "query", NS_SEARCH);
405
406 if (wocky_stanza_extract_errors (reply_msg, NULL, &err, NULL, NULL))
407 {
408 /* pass */
409 }
410 else if (NULL == query_node)
411 {
412 err = g_error_new (TP_ERROR, TP_ERROR_NOT_AVAILABLE,
413 "%s is broken: it replied to our <query> with an empty IQ",
414 chan->priv->server);
415 }
416 else
417 {
418 parse_search_field_response (chan, query_node);
419 }
420
421 if (err != NULL)
422 {
423 supported_field_discovery_failed (chan, err);
424 g_error_free (err);
425 }
426 }
427
428 static void
request_search_fields(GabbleSearchChannel * chan)429 request_search_fields (GabbleSearchChannel *chan)
430 {
431 TpBaseChannel *base = TP_BASE_CHANNEL (chan);
432 TpBaseConnection *base_conn = tp_base_channel_get_connection (base);
433 WockyStanza *msg;
434 GError *error = NULL;
435
436 msg = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET,
437 NULL, chan->priv->server,
438 '(', "query", ':', NS_SEARCH,
439 ')', NULL);
440
441 if (! _gabble_connection_send_with_reply (GABBLE_CONNECTION (base_conn), msg,
442 query_reply_cb, (GObject *) chan, NULL, &error))
443 {
444 supported_field_discovery_failed (chan, error);
445 g_error_free (error);
446 }
447
448 g_object_unref (msg);
449 }
450
451 /* Search implementation */
452
453 /**
454 * change_search_state:
455 * @chan: a search channel
456 * @state: the new state for the channel
457 * @reason: an error in the TP_ERROR domain if the search has failed; NULL
458 * otherwise.
459 */
460 static void
change_search_state(GabbleSearchChannel * chan,TpChannelContactSearchState state,const GError * reason)461 change_search_state (GabbleSearchChannel *chan,
462 TpChannelContactSearchState state,
463 const GError *reason)
464 {
465 GabbleSearchChannelPrivate *priv = chan->priv;
466 GHashTable *details = g_hash_table_new (g_str_hash, g_str_equal);
467 const gchar *error_name = NULL;
468 GValue v = { 0, };
469
470 switch (state)
471 {
472 case TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED:
473 /* Gabble shouldn't ever get into state More_Available */
474 case TP_CHANNEL_CONTACT_SEARCH_STATE_MORE_AVAILABLE:
475 g_assert_not_reached ();
476 return;
477 case TP_CHANNEL_CONTACT_SEARCH_STATE_IN_PROGRESS:
478 g_assert (priv->state == TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED);
479 break;
480 case TP_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED:
481 case TP_CHANNEL_CONTACT_SEARCH_STATE_FAILED:
482 g_assert (priv->state == TP_CHANNEL_CONTACT_SEARCH_STATE_IN_PROGRESS);
483 break;
484 }
485
486 if (state == TP_CHANNEL_CONTACT_SEARCH_STATE_FAILED)
487 {
488 g_assert (reason != NULL);
489 g_assert (reason->domain == TP_ERROR);
490 error_name = tp_error_get_dbus_name (reason->code);
491
492 g_value_init (&v, G_TYPE_STRING);
493 g_value_set_static_string (&v, reason->message);
494 g_hash_table_insert (details, "debug-message", &v);
495 }
496 else
497 {
498 g_assert (reason == NULL);
499 }
500
501 DEBUG ("moving from %s to %s for reason '%s'", states[priv->state],
502 states[state], error_name == NULL ? "" : error_name);
503 priv->state = state;
504
505 tp_svc_channel_type_contact_search_emit_search_state_changed (
506 chan, state, (error_name == NULL ? "" : error_name), details);
507
508 g_hash_table_unref (details);
509 }
510
511 /**
512 * make_field:
513 * @field_name: name of a vCard field; must be a static string.
514 * @values: strv of values for the field.
515 *
516 * Returns: the Contact_Info_Field (field_name, [], values).
517 */
518 static GValueArray *
make_field(const gchar * field_name,gchar ** values)519 make_field (const gchar *field_name,
520 gchar **values)
521 {
522 GValueArray *field = g_value_array_new (3);
523 GValue *value;
524 static const gchar **empty = { NULL };
525
526 g_value_array_append (field, NULL);
527 value = g_value_array_get_nth (field, 0);
528 g_value_init (value, G_TYPE_STRING);
529 g_value_set_static_string (value, field_name);
530
531 g_value_array_append (field, NULL);
532 value = g_value_array_get_nth (field, 1);
533 g_value_init (value, G_TYPE_STRV);
534 g_value_set_static_boxed (value, empty);
535
536 g_value_array_append (field, NULL);
537 value = g_value_array_get_nth (field, 2);
538 g_value_init (value, G_TYPE_STRV);
539 g_value_set_boxed (value, values);
540
541 return field;
542 }
543
544 static gchar *
ht_lookup_and_remove(GHashTable * info_map,gchar * field_name)545 ht_lookup_and_remove (GHashTable *info_map,
546 gchar *field_name)
547 {
548 gchar *ret = g_hash_table_lookup (info_map, field_name);
549
550 g_hash_table_remove (info_map, field_name);
551
552 return ret;
553 }
554
555 static void
add_search_result(GabbleSearchChannel * chan,GHashTable * info_map)556 add_search_result (GabbleSearchChannel *chan,
557 GHashTable *info_map)
558 {
559 GPtrArray *info = g_ptr_array_new ();
560 gchar *jid, *first = NULL, *last = NULL;
561 gpointer key, value;
562 GHashTableIter iter;
563
564 jid = ht_lookup_and_remove (info_map, "jid");
565 if (jid == NULL)
566 {
567 DEBUG ("no jid; giving up");
568 return;
569 }
570
571 if (!wocky_decode_jid (jid, NULL, NULL, NULL))
572 {
573 DEBUG ("'%s' is not a valid jid; ignoring this result", jid);
574 return;
575 }
576
577 g_hash_table_iter_init (&iter, info_map);
578 while (g_hash_table_iter_next (&iter, &key, &value))
579 {
580 const gchar *tp_name;
581 gchar *components[] = { value, NULL };
582
583 tp_name = g_hash_table_lookup (xmpp_to_tp, key);
584 if (tp_name == NULL)
585 {
586 DEBUG ("<item> contained field we don't understand (%s); ignoring it:"
587 , (const gchar *) key);
588 continue;
589 }
590
591 if (value == NULL)
592 {
593 DEBUG ("field %s (%s) doesn't have a value; ignoring it",
594 (const gchar *) key, tp_name);
595 continue;
596 }
597
598 DEBUG ("found field %s (%s): %s", (const gchar *) key, tp_name,
599 (const gchar *) value);
600
601 g_ptr_array_add (info, make_field (tp_name, components));
602
603 if (!tp_strdiff (key, "last") ||
604 !tp_strdiff (key, "family"))
605 last = value;
606 else if (!tp_strdiff (key, "first") ||
607 !tp_strdiff (key, "given"))
608 first = value;
609 }
610
611 /* Build 'n' field: Family Name, Given Name, Additional Names, Honorific
612 * Prefixes, and Honorific Suffixes.
613 */
614 if (last != NULL || first != NULL)
615 {
616 gchar *components[] = {
617 (last == NULL ? "" : last),
618 (first == NULL ? "" : first),
619 "",
620 "",
621 "",
622 NULL
623 };
624 g_ptr_array_add (info, make_field ("n", components));
625 }
626
627 g_hash_table_insert (chan->priv->results, g_strdup (jid), info);
628 }
629
630 static void
parse_result_item(GabbleSearchChannel * chan,WockyNode * item)631 parse_result_item (GabbleSearchChannel *chan,
632 WockyNode *item)
633 {
634 const gchar *jid = wocky_node_get_attribute (item, "jid");
635 GHashTable *info;
636 WockyNodeIter i;
637 WockyNode *n;
638
639 if (jid == NULL)
640 {
641 DEBUG ("<item> didn't have a jid attribute; skipping");
642 return;
643 }
644
645 info = g_hash_table_new (g_str_hash, g_str_equal);
646 g_hash_table_insert (info, "jid", (gchar *) jid);
647
648 wocky_node_iter_init (&i, item, NULL, NULL);
649 while (wocky_node_iter_next (&i, &n))
650 {
651 gchar *value = (gchar *) n->content;
652
653 g_hash_table_insert (info, n->name, value);
654 }
655
656 add_search_result (chan, info);
657 g_hash_table_unref (info);
658 }
659
660 static void
parse_extended_result_item(GabbleSearchChannel * chan,WockyNode * item)661 parse_extended_result_item (GabbleSearchChannel *chan,
662 WockyNode *item)
663 {
664 GHashTable *info = g_hash_table_new (g_str_hash, g_str_equal);
665 WockyNodeIter i;
666 WockyNode *field;
667
668 wocky_node_iter_init (&i, item, "field", NULL);
669 while (wocky_node_iter_next (&i, &field))
670 {
671 WockyNode *value_node;
672 const gchar *var, *value;
673
674 var = wocky_node_get_attribute (field, "var");
675 if (var == NULL)
676 {
677 DEBUG ("Ignore <field/> without 'var' attribut");
678 continue;
679 }
680
681 value_node = wocky_node_get_child (field, "value");
682 if (value_node == NULL)
683 {
684 DEBUG ("Ignore <field/> without <value/> child");
685 continue;
686 }
687
688 value = value_node->content;
689
690 g_hash_table_insert (info, (gchar *) var, (gchar *) value);
691 }
692
693 if (g_hash_table_lookup (info, "jid") == NULL)
694 {
695 DEBUG ("<item> didn't have a jid attribute; skipping");
696 }
697 else
698 {
699 add_search_result (chan, info);
700 }
701
702 g_hash_table_unref (info);
703 }
704
705 static gboolean
parse_unextended_search_results(GabbleSearchChannel * chan,WockyNode * query_node,GError ** error)706 parse_unextended_search_results (GabbleSearchChannel *chan,
707 WockyNode *query_node,
708 GError **error)
709 {
710 WockyNodeIter i;
711 WockyNode *item;
712
713 wocky_node_iter_init (&i, query_node, "item", NULL);
714 while (wocky_node_iter_next (&i, &item))
715 {
716 parse_result_item (chan, item);
717 }
718
719 return TRUE;
720 }
721
722 static gboolean
parse_extended_search_results(GabbleSearchChannel * chan,WockyNode * query_node,GError ** error)723 parse_extended_search_results (GabbleSearchChannel *chan,
724 WockyNode *query_node,
725 GError **error)
726 {
727 WockyNode *x, *item;
728 WockyNodeIter i;
729
730 x = wocky_node_get_child_ns (query_node, "x", NS_X_DATA);
731 if (x == NULL)
732 {
733 g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
734 "reply doens't contain a <x> node");
735 return FALSE;
736 }
737
738 wocky_node_iter_init (&i, x, NULL, NULL);
739 while (wocky_node_iter_next (&i, &item))
740 {
741 if (!tp_strdiff (item->name, "item"))
742 parse_extended_result_item (chan, item);
743 else if (!tp_strdiff (item->name, "reported"))
744 /* Ignore <reported> node */
745 continue;
746 else if (!tp_strdiff (item->name, "title"))
747 DEBUG ("title: %s", item->content);
748 else
749 DEBUG ("found <%s/> in <x/> rather than <item/>, <title/> and "
750 "<reported/>, skipping", item->name);
751 }
752
753 return TRUE;
754 }
755
756 static gboolean
parse_search_results(GabbleSearchChannel * chan,WockyNode * query_node,GError ** error)757 parse_search_results (GabbleSearchChannel *chan,
758 WockyNode *query_node,
759 GError **error)
760 {
761 if (chan->priv->xforms)
762 return parse_extended_search_results (chan, query_node, error);
763 else
764 return parse_unextended_search_results (chan, query_node, error);
765 }
766
767 static void
search_reply_cb(GabbleConnection * conn,WockyStanza * sent_msg,WockyStanza * reply_msg,GObject * object,gpointer user_data)768 search_reply_cb (GabbleConnection *conn,
769 WockyStanza *sent_msg,
770 WockyStanza *reply_msg,
771 GObject *object,
772 gpointer user_data)
773 {
774 GabbleSearchChannel *chan = GABBLE_SEARCH_CHANNEL (object);
775 WockyNode *query_node;
776 GError *stanza_error = NULL;
777 GError *err = NULL;
778
779 DEBUG ("called");
780
781 if (chan->priv->state != TP_CHANNEL_CONTACT_SEARCH_STATE_IN_PROGRESS)
782 {
783 DEBUG ("state is %s, not in progress; ignoring results",
784 states[chan->priv->state]);
785 return;
786 }
787
788 query_node = wocky_node_get_child_ns (
789 wocky_stanza_get_top_node (reply_msg), "query", NS_SEARCH);
790
791 if (wocky_stanza_extract_errors (reply_msg, NULL, &stanza_error, NULL, NULL))
792 {
793 gabble_set_tp_error_from_wocky (stanza_error, &err);
794 g_clear_error (&stanza_error);
795 }
796 else if (NULL == query_node)
797 {
798 err = g_error_new (TP_ERROR, TP_ERROR_NOT_AVAILABLE,
799 "%s is broken: its iq reply didn't contain a <query/>",
800 chan->priv->server);
801 }
802 else
803 {
804 parse_search_results (chan, query_node, &err);
805 }
806
807 if (err == NULL)
808 {
809 /* fire SearchStateChanged */
810 tp_svc_channel_type_contact_search_emit_search_result_received (chan,
811 chan->priv->results);
812
813 change_search_state (chan, TP_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED,
814 NULL);
815 }
816 else
817 {
818 DEBUG ("Searching failed: %s", err->message);
819 change_search_state (chan, TP_CHANNEL_CONTACT_SEARCH_STATE_FAILED,
820 err);
821 g_error_free (err);
822 }
823 }
824
825 static gboolean
validate_terms(GabbleSearchChannel * chan,GHashTable * terms,GError ** error)826 validate_terms (GabbleSearchChannel *chan,
827 GHashTable *terms,
828 GError **error)
829 {
830 const gchar * const *asks =
831 (const gchar * const *) chan->priv->available_search_keys;
832 GHashTableIter iter;
833 gpointer key;
834
835 g_hash_table_iter_init (&iter, terms);
836
837 while (g_hash_table_iter_next (&iter, &key, NULL))
838 {
839 gchar *field = key;
840
841 if (!tp_strv_contains (asks, field))
842 {
843 DEBUG ("%s is not in AvailableSearchKeys", field);
844 g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
845 "%s is not in AvailableSearchKeys", field);
846 return FALSE;
847 }
848 }
849
850 return TRUE;
851 }
852
853 static void
build_unextended_query(GabbleSearchChannel * self,WockyNode * query,GHashTable * terms)854 build_unextended_query (
855 GabbleSearchChannel *self,
856 WockyNode *query,
857 GHashTable *terms)
858 {
859 GHashTableIter iter;
860 gpointer key, value;
861
862 g_hash_table_iter_init (&iter, terms);
863
864 while (g_hash_table_iter_next (&iter, &key, &value))
865 {
866 gchar *xmpp_field = g_hash_table_lookup (self->priv->tp_to_xmpp, key);
867
868 g_assert (xmpp_field != NULL);
869
870 wocky_node_add_child_with_content (query, xmpp_field, value);
871 }
872 }
873
874 static void
build_extended_query(GabbleSearchChannel * self,WockyNode * query,GHashTable * terms)875 build_extended_query (GabbleSearchChannel *self,
876 WockyNode *query,
877 GHashTable *terms)
878 {
879 WockyNode *x, *field;
880 GHashTableIter iter;
881 gpointer key, value;
882
883 x = wocky_node_add_child_ns_q (query, "x",
884 g_quark_from_static_string (NS_X_DATA));
885 wocky_node_set_attribute (x, "type", "submit");
886
887 /* add FORM_TYPE */
888 field = wocky_node_add_child (x, "field");
889 wocky_node_set_attributes (field,
890 "type", "hidden",
891 "var", "FORM_TYPE",
892 NULL);
893 wocky_node_add_child_with_content (field, "value", NS_SEARCH);
894
895 /* Add search terms */
896 g_hash_table_iter_init (&iter, terms);
897
898 while (g_hash_table_iter_next (&iter, &key, &value))
899 {
900 const gchar *tp_name = key;
901 gchar *xmpp_field = g_hash_table_lookup (self->priv->tp_to_xmpp, tp_name);
902
903 g_assert (xmpp_field != NULL);
904
905 field = wocky_node_add_child (x, "field");
906 wocky_node_set_attribute (field, "var", xmpp_field);
907 wocky_node_add_child_with_content (field, "value", value);
908
909 if (!tp_strdiff (tp_name, ""))
910 {
911 /* Open fire search. Tick all the boolean fields */
912 guint i;
913
914 for (i = 0; i < self->priv->boolean_keys->len; i++)
915 {
916 xmpp_field = g_ptr_array_index (self->priv->boolean_keys, i);
917
918 field = wocky_node_add_child (x, "field");
919 wocky_node_set_attributes (field,
920 "var", xmpp_field,
921 "type", "boolean",
922 NULL);
923 wocky_node_add_child_with_content (field, "value", "1");
924 }
925 }
926 }
927 }
928
929 static gboolean
do_search(GabbleSearchChannel * chan,GHashTable * terms,GError ** error)930 do_search (GabbleSearchChannel *chan,
931 GHashTable *terms,
932 GError **error)
933 {
934 TpBaseChannel *base = TP_BASE_CHANNEL (chan);
935 TpBaseConnection *base_conn = tp_base_channel_get_connection (base);
936 WockyStanza *msg;
937 WockyNode *query;
938 gboolean ret;
939
940 DEBUG ("called");
941
942 if (!validate_terms (chan, terms, error))
943 return FALSE;
944
945 msg = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
946 NULL, chan->priv->server,
947 '(', "query", ':', NS_SEARCH,
948 '*', &query,
949 ')', NULL);
950
951 if (chan->priv->xforms)
952 {
953 build_extended_query (chan, query, terms);
954 }
955 else
956 {
957 build_unextended_query (chan, query, terms);
958 }
959
960 DEBUG ("Sending search");
961
962 if (_gabble_connection_send_with_reply (GABBLE_CONNECTION (base_conn), msg,
963 search_reply_cb, (GObject *) chan, NULL, error))
964 {
965 ret = TRUE;
966 change_search_state (chan,
967 TP_CHANNEL_CONTACT_SEARCH_STATE_IN_PROGRESS, NULL);
968 }
969 else
970 {
971 ret = FALSE;
972 }
973
974 g_object_unref (msg);
975 return ret;
976 }
977
978 /* GObject implementation */
979
980 static void
gabble_search_channel_init(GabbleSearchChannel * self)981 gabble_search_channel_init (GabbleSearchChannel *self)
982 {
983 GabbleSearchChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
984 GABBLE_TYPE_SEARCH_CHANNEL, GabbleSearchChannelPrivate);
985
986 self->priv = priv;
987 }
988
989 static void
free_info(GPtrArray * info)990 free_info (GPtrArray *info)
991 {
992 g_boxed_free (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, info);
993 }
994
995 static GObject *
gabble_search_channel_constructor(GType type,guint n_props,GObjectConstructParam * props)996 gabble_search_channel_constructor (GType type,
997 guint n_props,
998 GObjectConstructParam *props)
999 {
1000 GObject *obj;
1001 GabbleSearchChannel *chan;
1002 TpBaseChannel *base;
1003 TpBaseConnection *conn;
1004
1005 obj = G_OBJECT_CLASS (gabble_search_channel_parent_class)->constructor (
1006 type, n_props, props);
1007 chan = GABBLE_SEARCH_CHANNEL (obj);
1008 base = TP_BASE_CHANNEL (obj);
1009 conn = tp_base_channel_get_connection (base);
1010
1011 chan->priv->result_handles = tp_handle_set_new (
1012 tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT));
1013
1014 chan->priv->tp_to_xmpp = g_hash_table_new_full (g_str_hash, g_str_equal,
1015 g_free, g_free);
1016
1017 chan->priv->boolean_keys = g_ptr_array_new ();
1018
1019 chan->priv->results = g_hash_table_new_full (g_str_hash, g_str_equal,
1020 g_free, (GDestroyNotify) free_info);
1021
1022 request_search_fields (chan);
1023
1024 return obj;
1025 }
1026
1027 static void
gabble_search_channel_finalize(GObject * obj)1028 gabble_search_channel_finalize (GObject *obj)
1029 {
1030 GabbleSearchChannel *chan = GABBLE_SEARCH_CHANNEL (obj);
1031 GabbleSearchChannelPrivate *priv = chan->priv;
1032 guint i;
1033
1034 DEBUG ("bye bye %p", obj);
1035
1036 g_free (priv->server);
1037
1038 tp_handle_set_destroy (priv->result_handles);
1039 g_hash_table_unref (chan->priv->tp_to_xmpp);
1040
1041 g_free (chan->priv->available_search_keys);
1042
1043 for (i = 0; i < priv->boolean_keys->len; i++)
1044 {
1045 g_free (g_ptr_array_index (priv->boolean_keys, i));
1046 }
1047 g_ptr_array_unref (priv->boolean_keys);
1048
1049 g_hash_table_unref (chan->priv->results);
1050
1051 if (G_OBJECT_CLASS (gabble_search_channel_parent_class)->finalize)
1052 G_OBJECT_CLASS (gabble_search_channel_parent_class)->finalize (obj);
1053 }
1054
1055 static void
gabble_search_channel_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)1056 gabble_search_channel_get_property (GObject *object,
1057 guint property_id,
1058 GValue *value,
1059 GParamSpec *pspec)
1060 {
1061 GabbleSearchChannel *chan = GABBLE_SEARCH_CHANNEL (object);
1062
1063 switch (property_id)
1064 {
1065 case PROP_SEARCH_STATE:
1066 g_value_set_uint (value, chan->priv->state);
1067 break;
1068 case PROP_AVAILABLE_SEARCH_KEYS:
1069 g_value_set_boxed (value, chan->priv->available_search_keys);
1070 break;
1071 case PROP_SERVER:
1072 g_value_set_string (value, chan->priv->server);
1073 break;
1074 case PROP_LIMIT:
1075 g_value_set_uint (value, 0);
1076 break;
1077 default:
1078 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1079 break;
1080 }
1081 }
1082
1083 static void
gabble_search_channel_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)1084 gabble_search_channel_set_property (GObject *object,
1085 guint property_id,
1086 const GValue *value,
1087 GParamSpec *pspec)
1088 {
1089 GabbleSearchChannel *chan = GABBLE_SEARCH_CHANNEL (object);
1090
1091 switch (property_id)
1092 {
1093 case PROP_SERVER:
1094 chan->priv->server = g_value_dup_string (value);
1095 g_assert (chan->priv->server != NULL);
1096 break;
1097 default:
1098 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1099 break;
1100 }
1101 }
1102
1103 static void
gabble_search_channel_fill_immutable_properties(TpBaseChannel * chan,GHashTable * properties)1104 gabble_search_channel_fill_immutable_properties (
1105 TpBaseChannel *chan,
1106 GHashTable *properties)
1107 {
1108 TP_BASE_CHANNEL_CLASS (gabble_search_channel_parent_class)->fill_immutable_properties (
1109 chan, properties);
1110
1111 tp_dbus_properties_mixin_fill_properties_hash (
1112 G_OBJECT (chan), properties,
1113 TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH, "AvailableSearchKeys",
1114 TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH, "Server",
1115 TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH, "Limit",
1116 NULL);
1117 }
1118
1119 static gchar *
gabble_search_channel_get_object_path_suffix(TpBaseChannel * base)1120 gabble_search_channel_get_object_path_suffix (TpBaseChannel *base)
1121 {
1122 GabbleSearchChannel *self = GABBLE_SEARCH_CHANNEL (base);
1123 gchar *escaped, *ret;
1124
1125 escaped = tp_escape_as_identifier (self->priv->server);
1126 ret = g_strdup_printf ("SearchChannel_%s_%p", escaped, self);
1127 g_free (escaped);
1128
1129 return ret;
1130 }
1131
1132 static void
gabble_search_channel_class_init(GabbleSearchChannelClass * klass)1133 gabble_search_channel_class_init (GabbleSearchChannelClass *klass)
1134 {
1135 static TpDBusPropertiesMixinPropImpl search_channel_props[] = {
1136 { "SearchState", "search-state", NULL },
1137 { "AvailableSearchKeys", "available-search-keys", NULL },
1138 { "Server", "server", NULL },
1139 { "Limit", "limit", NULL },
1140 { NULL }
1141 };
1142 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1143 TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS (klass);
1144 GParamSpec *param_spec;
1145
1146 g_type_class_add_private (klass, sizeof (GabbleSearchChannelPrivate));
1147
1148 object_class->constructor = gabble_search_channel_constructor;
1149 object_class->finalize = gabble_search_channel_finalize;
1150
1151 object_class->get_property = gabble_search_channel_get_property;
1152 object_class->set_property = gabble_search_channel_set_property;
1153
1154 base_class->channel_type = TP_IFACE_CHANNEL_TYPE_CONTACT_SEARCH;
1155 base_class->target_handle_type = TP_HANDLE_TYPE_NONE;
1156 base_class->fill_immutable_properties =
1157 gabble_search_channel_fill_immutable_properties;
1158 base_class->get_object_path_suffix =
1159 gabble_search_channel_get_object_path_suffix;
1160 /* We don't have to do any special clean-up when told to close, so we can
1161 * just roll over and die immediately.
1162 */
1163 base_class->close = tp_base_channel_destroyed;
1164
1165 param_spec = g_param_spec_uint ("search-state", "Search state",
1166 "The current state of the search represented by this channel",
1167 TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED,
1168 TP_CHANNEL_CONTACT_SEARCH_STATE_FAILED,
1169 TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED,
1170 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1171 g_object_class_install_property (object_class, PROP_SEARCH_STATE,
1172 param_spec);
1173
1174 param_spec = g_param_spec_boxed ("available-search-keys",
1175 "Available search keys",
1176 "The set of search keys supported by this channel",
1177 G_TYPE_STRV,
1178 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1179 g_object_class_install_property (object_class, PROP_AVAILABLE_SEARCH_KEYS,
1180 param_spec);
1181
1182 param_spec = g_param_spec_string ("server", "Search server",
1183 "The user directory server used by this search",
1184 NULL,
1185 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1186 g_object_class_install_property (object_class, PROP_SERVER, param_spec);
1187
1188 param_spec = g_param_spec_uint ("limit", "Result limit",
1189 "Always 0 for unlimited in Gabble",
1190 0, 0, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1191 g_object_class_install_property (object_class, PROP_LIMIT,
1192 param_spec);
1193
1194 /* Emitted when we get a reply from the server about which search keys it
1195 * supports. Its three arguments are the components of a GError. If the
1196 * server gave us a set of search keys, and they were sane, all components
1197 * will be 0 or %NULL, indicating that this channel can be announced and
1198 * used; if the server doesn't actually speak XEP 0055 or is full of bees,
1199 * they'll be an error in either the GABBLE_XMPP_ERROR or the TP_ERROR
1200 * domain.
1201 */
1202 signals[READY_OR_NOT] =
1203 g_signal_new ("ready-or-not",
1204 G_OBJECT_CLASS_TYPE (klass),
1205 G_SIGNAL_RUN_LAST,
1206 0,
1207 NULL, NULL,
1208 gabble_marshal_VOID__UINT_INT_STRING,
1209 G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING);
1210
1211 tp_dbus_properties_mixin_implement_interface (object_class,
1212 TP_IFACE_QUARK_CHANNEL_TYPE_CONTACT_SEARCH,
1213 tp_dbus_properties_mixin_getter_gobject_properties, NULL,
1214 search_channel_props);
1215
1216 build_mapping_tables ();
1217 }
1218
1219 static void
gabble_search_channel_search(TpSvcChannelTypeContactSearch * self,GHashTable * terms,DBusGMethodInvocation * context)1220 gabble_search_channel_search (TpSvcChannelTypeContactSearch *self,
1221 GHashTable *terms,
1222 DBusGMethodInvocation *context)
1223 {
1224 GabbleSearchChannel *chan = GABBLE_SEARCH_CHANNEL (self);
1225 GabbleSearchChannelPrivate *priv = chan->priv;
1226 GError *error = NULL;
1227
1228 if (priv->state != TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED)
1229 {
1230 error = g_error_new (TP_ERROR, TP_ERROR_NOT_AVAILABLE,
1231 "SearchState is %s", states[priv->state]);
1232 goto err;
1233 }
1234
1235 if (do_search (chan, terms, &error))
1236 {
1237 tp_svc_channel_type_contact_search_return_from_search (context);
1238 return;
1239 }
1240
1241 err:
1242 dbus_g_method_return_error (context, error);
1243 g_error_free (error);
1244 }
1245
1246 static void
gabble_search_channel_stop(TpSvcChannelTypeContactSearch * self,DBusGMethodInvocation * context)1247 gabble_search_channel_stop (TpSvcChannelTypeContactSearch *self,
1248 DBusGMethodInvocation *context)
1249 {
1250 GabbleSearchChannel *chan = GABBLE_SEARCH_CHANNEL (self);
1251 GabbleSearchChannelPrivate *priv = chan->priv;
1252
1253 switch (priv->state)
1254 {
1255 case TP_CHANNEL_CONTACT_SEARCH_STATE_IN_PROGRESS:
1256 {
1257 GError e = { TP_ERROR, TP_ERROR_CANCELLED, "Stop() called" };
1258
1259 change_search_state (chan,
1260 TP_CHANNEL_CONTACT_SEARCH_STATE_FAILED, &e);
1261 /* Deliberately falling through to return from the method: */
1262 }
1263 case TP_CHANNEL_CONTACT_SEARCH_STATE_COMPLETED:
1264 case TP_CHANNEL_CONTACT_SEARCH_STATE_FAILED:
1265 tp_svc_channel_type_contact_search_return_from_stop (context);
1266 break;
1267 case TP_CHANNEL_CONTACT_SEARCH_STATE_NOT_STARTED:
1268 {
1269 GError e = { TP_ERROR, TP_ERROR_NOT_AVAILABLE,
1270 "Search() hasn't been called yet" };
1271
1272 dbus_g_method_return_error (context, &e);
1273 break;
1274 }
1275 case TP_CHANNEL_CONTACT_SEARCH_STATE_MORE_AVAILABLE:
1276 g_assert_not_reached ();
1277 }
1278 }
1279
1280 gboolean
gabble_search_channel_is_ready(GabbleSearchChannel * self)1281 gabble_search_channel_is_ready (GabbleSearchChannel *self)
1282 {
1283 return self->priv->ready;
1284 }
1285
1286 static void
contact_search_iface_init(gpointer g_iface,gpointer iface_data)1287 contact_search_iface_init (gpointer g_iface,
1288 gpointer iface_data)
1289 {
1290 TpSvcChannelTypeContactSearchClass *klass = g_iface;
1291
1292 #define IMPLEMENT(x) tp_svc_channel_type_contact_search_implement_##x (\
1293 klass, gabble_search_channel_##x)
1294 IMPLEMENT(search);
1295 IMPLEMENT(stop);
1296 #undef IMPLEMENT
1297 }
1298