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