1 /*
2  * message.c - Source for TpMessage
3  * Copyright (C) 2006-2010 Collabora Ltd.
4  * Copyright (C) 2006-2008 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 /**
22  * SECTION:message
23  * @title: TpMessage
24  * @short_description: a message in the Telepathy message interface
25  * @see_also: #TpCMMessage, #TpClientMessage, #TpSignalledMessage
26  *
27  * #TpMessage represent a message send or received using the Message
28  * interface.
29  *
30  * Since: 0.7.21
31  */
32 
33 #include "config.h"
34 
35 #include "message.h"
36 #include "message-internal.h"
37 
38 #include <telepathy-glib/cm-message.h>
39 #include <telepathy-glib/dbus.h>
40 #include <telepathy-glib/gtypes.h>
41 #include <telepathy-glib/util.h>
42 #include <telepathy-glib/variant-util-internal.h>
43 
44 #define DEBUG_FLAG TP_DEBUG_MISC
45 #include "telepathy-glib/debug-internal.h"
46 
47 G_DEFINE_TYPE (TpMessage, tp_message, G_TYPE_OBJECT)
48 
49 /**
50  * TpMessage:
51  *
52  * Opaque structure representing a message in the Telepathy messages interface
53  * (an array of at least one mapping from string to variant, where the first
54  * mapping contains message headers and subsequent mappings contain the
55  * message body).
56  *
57  * This base class provides convenience API for most of the common keys that
58  * can appear in the header. One notable exception is the sender of the
59  * message. Inside a connection manager, messages are represented by the
60  * #TpCMMessage subclass, and you should use tp_cm_message_get_sender().
61  * When composing a message in a client using #TpClientMessage, messages do
62  * not have an explicit sender (the sender is automatically the local user).
63  * When a client sees a sent or received message signalled by the connection
64  * manager (represented by #TpSignalledMessage), the message's sender (if any)
65  * can be accessed with tp_signalled_message_get_sender().
66  */
67 
68 struct _TpMessagePrivate
69 {
70   gboolean mutable;
71 };
72 
73 static void
tp_message_dispose(GObject * object)74 tp_message_dispose (GObject *object)
75 {
76   TpMessage *self = TP_MESSAGE (object);
77   void (*dispose) (GObject *) =
78     G_OBJECT_CLASS (tp_message_parent_class)->dispose;
79   guint i;
80 
81   if (self->parts != NULL)
82     {
83       for (i = 0; i < self->parts->len; i++)
84         {
85           g_hash_table_unref (g_ptr_array_index (self->parts, i));
86         }
87 
88       g_ptr_array_unref (self->parts);
89       self->parts = NULL;
90     }
91 
92   if (dispose != NULL)
93     dispose (object);
94 }
95 
96 static void
tp_message_class_init(TpMessageClass * klass)97 tp_message_class_init (TpMessageClass *klass)
98 {
99   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
100 
101   gobject_class->dispose = tp_message_dispose;
102 
103   g_type_class_add_private (gobject_class, sizeof (TpMessagePrivate));
104 }
105 
106 static void
tp_message_init(TpMessage * self)107 tp_message_init (TpMessage *self)
108 {
109   self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), TP_TYPE_MESSAGE,
110       TpMessagePrivate);
111 
112   /* Message can be modified until _tp_message_set_immutable() is called */
113   self->priv->mutable = TRUE;
114 
115   /* Create header part */
116   self->parts = g_ptr_array_sized_new (1);
117 
118   tp_message_append_part (self);
119 }
120 
121 
122 /**
123  * tp_message_new:
124  * @connection: a connection on which to reference handles
125  * @initial_parts: number of parts to create (at least 1)
126  * @size_hint: preallocate space for this many parts (at least @initial_parts)
127  *
128  * <!-- nothing more to say -->
129  *
130  * Returns: a newly allocated message suitable to be passed to
131  * tp_message_mixin_take_received()
132  *
133  * Since: 0.7.21
134  * Deprecated: since 0.13.9. Use tp_cm_message_new()
135  */
136 TpMessage *
tp_message_new(TpBaseConnection * connection,guint initial_parts,guint size_hint)137 tp_message_new (TpBaseConnection *connection,
138                 guint initial_parts,
139                 guint size_hint)
140 {
141   g_return_val_if_fail (size_hint >= initial_parts, NULL);
142 
143   return tp_cm_message_new (connection, initial_parts);
144 }
145 
146 
147 /**
148  * tp_message_destroy:
149  * @self: a message
150  *
151  * Since 0.13.9 this function is a simple wrapper around
152  * g_object_unref()
153  *
154  * Since: 0.7.21
155  */
156 void
tp_message_destroy(TpMessage * self)157 tp_message_destroy (TpMessage *self)
158 {
159   g_object_unref (self);
160 }
161 
162 /**
163  * tp_message_count_parts:
164  * @self: a message
165  *
166  * <!-- nothing more to say -->
167  *
168  * Returns: the number of parts in the message, including the headers in
169  * part 0
170  *
171  * Since: 0.7.21
172  */
173 guint
tp_message_count_parts(TpMessage * self)174 tp_message_count_parts (TpMessage *self)
175 {
176   return self->parts->len;
177 }
178 
179 /**
180  * tp_message_peek:
181  * @self: a message
182  * @part: a part number
183  *
184  * <!-- nothing more to say -->
185  *
186  * Returns: (transfer none) (element-type utf8 GObject.Value):
187  *  the #GHashTable used to implement the given part, or %NULL if the
188  *  part number is out of range. The hash table is only valid as long as the
189  *  message is valid and the part is not deleted.
190  *
191  * Since: 0.7.21
192  */
193 const GHashTable *
tp_message_peek(TpMessage * self,guint part)194 tp_message_peek (TpMessage *self,
195                  guint part)
196 {
197   if (part >= self->parts->len)
198     return NULL;
199 
200   return g_ptr_array_index (self->parts, part);
201 }
202 
203 /**
204  * tp_message_dup_part:
205  * @self: a message
206  * @part: a part number
207  *
208  * <!-- nothing more to say -->
209  *
210  * Returns: (transfer full):
211  *  the current contents of the given part, or %NULL if the part number is
212  *  out of range
213  *
214  * Since: 0.19.10
215  */
216 GVariant *
tp_message_dup_part(TpMessage * self,guint part)217 tp_message_dup_part (TpMessage *self,
218     guint part)
219 {
220   if (part >= self->parts->len)
221     return NULL;
222 
223   return _tp_asv_to_vardict (g_ptr_array_index (self->parts, part));
224 }
225 
226 
227 /**
228  * tp_message_append_part:
229  * @self: a message
230  *
231  * Append a body part to the message.
232  *
233  * Returns: the part number
234  *
235  * Since: 0.7.21
236  */
237 guint
tp_message_append_part(TpMessage * self)238 tp_message_append_part (TpMessage *self)
239 {
240   g_return_val_if_fail (self->priv->mutable, 0);
241 
242   g_ptr_array_add (self->parts, g_hash_table_new_full (g_str_hash,
243         g_str_equal, g_free, (GDestroyNotify) tp_g_value_slice_free));
244   return self->parts->len - 1;
245 }
246 
247 /**
248  * tp_message_delete_part:
249  * @self: a message
250  * @part: a part number, which must be strictly greater than 0, and strictly
251  *  less than the number returned by tp_message_count_parts()
252  *
253  * Delete the given body part from the message.
254  *
255  * Since: 0.7.21
256  */
257 void
tp_message_delete_part(TpMessage * self,guint part)258 tp_message_delete_part (TpMessage *self,
259                         guint part)
260 {
261   g_return_if_fail (part < self->parts->len);
262   g_return_if_fail (part > 0);
263   g_return_if_fail (self->priv->mutable);
264 
265   g_hash_table_unref (g_ptr_array_remove_index (self->parts, part));
266 }
267 
268 /**
269  * tp_message_ref_handle:
270  * @self: a message
271  * @handle_type: a handle type, greater than %TP_HANDLE_TYPE_NONE and less than
272  *  %TP_NUM_HANDLE_TYPES
273  * @handle: a handle of the given type
274  *
275  * Reference the given handle until this message is destroyed.
276  *
277  * Since: 0.7.21
278  * Deprecated: since 0.13.9. Handles are now immortal so there is
279  * no point to ref them. Furthermore, the only handle that should be stored
280  * in a TpMessage is message-sender which should be set using
281  * tp_cm_message_set_sender().
282  */
283 void
tp_message_ref_handle(TpMessage * self,TpHandleType handle_type,TpHandle handle)284 tp_message_ref_handle (TpMessage *self,
285                        TpHandleType handle_type,
286                        TpHandle handle)
287 {
288   g_return_if_fail (TP_IS_CM_MESSAGE (self));
289   g_return_if_fail (self->priv->mutable);
290 
291   /* Handles are now immortal so we don't have to anything */
292 }
293 
294 /**
295  * tp_message_delete_key:
296  * @self: a message
297  * @part: a part number, which must be strictly less than the number
298  *  returned by tp_message_count_parts()
299  * @key: a key in the mapping representing the part
300  *
301  * Remove the given key and its value from the given part.
302  *
303  * Returns: %TRUE if the key previously existed
304  *
305  * Since: 0.7.21
306  */
307 gboolean
tp_message_delete_key(TpMessage * self,guint part,const gchar * key)308 tp_message_delete_key (TpMessage *self,
309                        guint part,
310                        const gchar *key)
311 {
312   g_return_val_if_fail (part < self->parts->len, FALSE);
313   g_return_val_if_fail (self->priv->mutable, FALSE);
314 
315   return g_hash_table_remove (g_ptr_array_index (self->parts, part), key);
316 }
317 
318 
319 /**
320  * tp_message_set_handle:
321  * @self: a #TpCMMessage
322  * @part: a part number, which must be strictly less than the number
323  *  returned by tp_message_count_parts()
324  * @key: a key in the mapping representing the part
325  * @handle_type: a handle type
326  * @handle_or_0: a handle of that type, or 0
327  *
328  * If @handle_or_0 is not zero, reference it with tp_message_ref_handle().
329  *
330  * Set @key in part @part of @self to have @handle_or_0 as an unsigned integer
331  * value.
332  *
333  * Since 0.13.9 this function has been deprecated in favor or
334  * tp_cm_message_set_sender() as 'message-sender' is the only handle
335  * you can put in a #TpCMMessage.
336  *
337  * Since: 0.7.21
338  * Deprecated: since 0.13.9. Use tp_cm_message_set_sender()
339  */
340 void
tp_message_set_handle(TpMessage * self,guint part,const gchar * key,TpHandleType handle_type,TpHandle handle_or_0)341 tp_message_set_handle (TpMessage *self,
342                        guint part,
343                        const gchar *key,
344                        TpHandleType handle_type,
345                        TpHandle handle_or_0)
346 {
347   g_return_if_fail (TP_IS_CM_MESSAGE (self));
348   g_return_if_fail (self->priv->mutable);
349 
350   tp_message_set_uint32 (self, part, key, handle_or_0);
351 }
352 
353 
354 /**
355  * tp_message_set_boolean:
356  * @self: a message
357  * @part: a part number, which must be strictly less than the number
358  *  returned by tp_message_count_parts()
359  * @key: a key in the mapping representing the part
360  * @b: a boolean value
361  *
362  * Set @key in part @part of @self to have @b as a boolean value.
363  *
364  * Since: 0.7.21
365  */
366 void
tp_message_set_boolean(TpMessage * self,guint part,const gchar * key,gboolean b)367 tp_message_set_boolean (TpMessage *self,
368                         guint part,
369                         const gchar *key,
370                         gboolean b)
371 {
372   g_return_if_fail (part < self->parts->len);
373   g_return_if_fail (key != NULL);
374   g_return_if_fail (self->priv->mutable);
375 
376   g_hash_table_insert (g_ptr_array_index (self->parts, part),
377       g_strdup (key), tp_g_value_slice_new_boolean (b));
378 }
379 
380 /**
381  * tp_message_set_int16:
382  * @s: a message
383  * @p: a part number, which must be strictly less than the number
384  *  returned by tp_message_count_parts()
385  * @k: a key in the mapping representing the part
386  * @i: an integer value
387  *
388  * Set @key in part @part of @self to have @i as a signed integer value.
389  *
390  * Since: 0.7.21
391  */
392 
393 /**
394  * tp_message_set_int32:
395  * @self: a message
396  * @part: a part number, which must be strictly less than the number
397  *  returned by tp_message_count_parts()
398  * @key: a key in the mapping representing the part
399  * @i: an integer value
400  *
401  * Set @key in part @part of @self to have @i as a signed integer value.
402  *
403  * Since: 0.7.21
404  */
405 void
tp_message_set_int32(TpMessage * self,guint part,const gchar * key,gint32 i)406 tp_message_set_int32 (TpMessage *self,
407                       guint part,
408                       const gchar *key,
409                       gint32 i)
410 {
411   g_return_if_fail (part < self->parts->len);
412   g_return_if_fail (key != NULL);
413   g_return_if_fail (self->priv->mutable);
414 
415   g_hash_table_insert (g_ptr_array_index (self->parts, part),
416       g_strdup (key), tp_g_value_slice_new_int (i));
417 }
418 
419 
420 /**
421  * tp_message_set_int64:
422  * @self: a message
423  * @part: a part number, which must be strictly less than the number
424  *  returned by tp_message_count_parts()
425  * @key: a key in the mapping representing the part
426  * @i: an integer value
427  *
428  * Set @key in part @part of @self to have @i as a signed integer value.
429  *
430  * Since: 0.7.21
431  */
432 void
tp_message_set_int64(TpMessage * self,guint part,const gchar * key,gint64 i)433 tp_message_set_int64 (TpMessage *self,
434                       guint part,
435                       const gchar *key,
436                       gint64 i)
437 {
438   g_return_if_fail (part < self->parts->len);
439   g_return_if_fail (key != NULL);
440   g_return_if_fail (self->priv->mutable);
441 
442   g_hash_table_insert (g_ptr_array_index (self->parts, part),
443       g_strdup (key), tp_g_value_slice_new_int64 (i));
444 }
445 
446 
447 /**
448  * tp_message_set_uint16:
449  * @s: a message
450  * @p: a part number, which must be strictly less than the number
451  *  returned by tp_message_count_parts()
452  * @k: a key in the mapping representing the part
453  * @u: an unsigned integer value
454  *
455  * Set @key in part @part of @self to have @u as an unsigned integer value.
456  *
457  * Since: 0.7.21
458  */
459 
460 /**
461  * tp_message_set_uint32:
462  * @self: a message
463  * @part: a part number, which must be strictly less than the number
464  *  returned by tp_message_count_parts()
465  * @key: a key in the mapping representing the part
466  * @u: an unsigned integer value
467  *
468  * Set @key in part @part of @self to have @u as an unsigned integer value.
469  *
470  * Since: 0.7.21
471  */
472 void
tp_message_set_uint32(TpMessage * self,guint part,const gchar * key,guint32 u)473 tp_message_set_uint32 (TpMessage *self,
474                        guint part,
475                        const gchar *key,
476                        guint32 u)
477 {
478   g_return_if_fail (part < self->parts->len);
479   g_return_if_fail (key != NULL);
480   g_return_if_fail (self->priv->mutable);
481 
482   g_hash_table_insert (g_ptr_array_index (self->parts, part),
483       g_strdup (key), tp_g_value_slice_new_uint (u));
484 }
485 
486 
487 /**
488  * tp_message_set_uint64:
489  * @self: a message
490  * @part: a part number, which must be strictly less than the number
491  *  returned by tp_message_count_parts()
492  * @key: a key in the mapping representing the part
493  * @u: an unsigned integer value
494  *
495  * Set @key in part @part of @self to have @u as an unsigned integer value.
496  *
497  * Since: 0.7.21
498  */
499 void
tp_message_set_uint64(TpMessage * self,guint part,const gchar * key,guint64 u)500 tp_message_set_uint64 (TpMessage *self,
501                        guint part,
502                        const gchar *key,
503                        guint64 u)
504 {
505   g_return_if_fail (part < self->parts->len);
506   g_return_if_fail (key != NULL);
507   g_return_if_fail (self->priv->mutable);
508 
509   g_hash_table_insert (g_ptr_array_index (self->parts, part),
510       g_strdup (key), tp_g_value_slice_new_uint64 (u));
511 }
512 
513 
514 /**
515  * tp_message_set_string:
516  * @self: a message
517  * @part: a part number, which must be strictly less than the number
518  *  returned by tp_message_count_parts()
519  * @key: a key in the mapping representing the part
520  * @s: a string value
521  *
522  * Set @key in part @part of @self to have @s as a string value.
523  *
524  * Since: 0.7.21
525  */
526 void
tp_message_set_string(TpMessage * self,guint part,const gchar * key,const gchar * s)527 tp_message_set_string (TpMessage *self,
528                        guint part,
529                        const gchar *key,
530                        const gchar *s)
531 {
532   g_return_if_fail (part < self->parts->len);
533   g_return_if_fail (key != NULL);
534   g_return_if_fail (s != NULL);
535   g_return_if_fail (self->priv->mutable);
536 
537   g_hash_table_insert (g_ptr_array_index (self->parts, part),
538       g_strdup (key), tp_g_value_slice_new_string (s));
539 }
540 
541 
542 /**
543  * tp_message_set_string_printf:
544  * @self: a message
545  * @part: a part number, which must be strictly less than the number
546  *  returned by tp_message_count_parts()
547  * @key: a key in the mapping representing the part
548  * @fmt: a printf-style format string for the string value
549  * @...: arguments for the format string
550  *
551  * Set @key in part @part of @self to have a string value constructed from a
552  * printf-style format string.
553  *
554  * Since: 0.7.21
555  */
556 void
tp_message_set_string_printf(TpMessage * self,guint part,const gchar * key,const gchar * fmt,...)557 tp_message_set_string_printf (TpMessage *self,
558                               guint part,
559                               const gchar *key,
560                               const gchar *fmt,
561                               ...)
562 {
563   va_list va;
564   gchar *s;
565 
566   g_return_if_fail (part < self->parts->len);
567   g_return_if_fail (key != NULL);
568   g_return_if_fail (fmt != NULL);
569   g_return_if_fail (self->priv->mutable);
570 
571   va_start (va, fmt);
572   s = g_strdup_vprintf (fmt, va);
573   va_end (va);
574 
575   g_hash_table_insert (g_ptr_array_index (self->parts, part),
576       g_strdup (key), tp_g_value_slice_new_take_string (s));
577 }
578 
579 
580 /**
581  * tp_message_set_bytes:
582  * @self: a message
583  * @part: a part number, which must be strictly less than the number
584  *  returned by tp_message_count_parts()
585  * @key: a key in the mapping representing the part
586  * @len: a number of bytes
587  * @bytes: an array of @len bytes
588  *
589  * Set @key in part @part of @self to have @bytes as a byte-array value.
590  *
591  * Since: 0.7.21
592  */
593 void
tp_message_set_bytes(TpMessage * self,guint part,const gchar * key,guint len,gconstpointer bytes)594 tp_message_set_bytes (TpMessage *self,
595                       guint part,
596                       const gchar *key,
597                       guint len,
598                       gconstpointer bytes)
599 {
600   g_return_if_fail (part < self->parts->len);
601   g_return_if_fail (key != NULL);
602   g_return_if_fail (bytes != NULL);
603   g_return_if_fail (self->priv->mutable);
604 
605   g_hash_table_insert (g_ptr_array_index (self->parts, part),
606       g_strdup (key),
607       tp_g_value_slice_new_bytes (len, bytes));
608 }
609 
610 
611 /**
612  * tp_message_set:
613  * @self: a message
614  * @part: a part number, which must be strictly less than the number
615  *  returned by tp_message_count_parts()
616  * @key: a key in the mapping representing the part
617  * @source: a value, encoded as dbus-glib would
618  *
619  * Set @key in part @part of @self to have a copy of @source as its value.
620  *
621  * If @source represents a data structure containing handles, they should
622  * all be referenced with tp_message_ref_handle() first.
623  *
624  * In high-level language bindings, use tp_message_set_variant() instead.
625  *
626  * Since: 0.7.21
627  */
628 void
tp_message_set(TpMessage * self,guint part,const gchar * key,const GValue * source)629 tp_message_set (TpMessage *self,
630                 guint part,
631                 const gchar *key,
632                 const GValue *source)
633 {
634   g_return_if_fail (part < self->parts->len);
635   g_return_if_fail (key != NULL);
636   g_return_if_fail (source != NULL);
637   g_return_if_fail (self->priv->mutable);
638 
639   g_hash_table_insert (g_ptr_array_index (self->parts, part),
640       g_strdup (key), tp_g_value_slice_dup (source));
641 }
642 
643 /**
644  * tp_message_set_variant:
645  * @self: a message
646  * @part: a part number, which must be strictly less than the number
647  *  returned by tp_message_count_parts()
648  * @key: a key in the mapping representing the part
649  * @value: a value
650  *
651  * Set @key in part @part of @self to have @value as its value.
652  *
653  * If @value is a floating reference (see g_variant_ref_sink()), then this
654  * function will take ownership of it.
655  *
656  * Since: 0.19.10
657  */
658 void
tp_message_set_variant(TpMessage * self,guint part,const gchar * key,GVariant * value)659 tp_message_set_variant (TpMessage *self,
660     guint part,
661     const gchar *key,
662     GVariant *value)
663 {
664   GValue *gvalue;
665 
666   g_return_if_fail (part < self->parts->len);
667   g_return_if_fail (key != NULL);
668   g_return_if_fail (value != NULL);
669   g_return_if_fail (self->priv->mutable);
670 
671   g_variant_ref_sink (value);
672   gvalue = g_slice_new0 (GValue);
673   dbus_g_value_parse_g_variant (value, gvalue);
674   g_variant_unref (value);
675 
676   g_hash_table_insert (g_ptr_array_index (self->parts, part),
677       g_strdup (key), gvalue);
678 }
679 
680 /**
681  * tp_message_take_message:
682  * @self: a #TpCMMessage
683  * @part: a part number, which must be strictly less than the number
684  *  returned by tp_message_count_parts()
685  * @key: a key in the mapping representing the part
686  * @message: another (distinct) message created for the same #TpBaseConnection
687  *
688  * Set @key in part @part of @self to have @message as an aa{sv} value (that
689  * is, an array of Message_Part), and take ownership of @message.  The caller
690  * should not use @message after passing it to this function.  All handle
691  * references owned by @message will subsequently belong to and be released
692  * with @self.
693  *
694  * Since: 0.7.21
695  * Deprecated: since 0.13.9. Use tp_cm_message_take_message()
696  */
697 void
tp_message_take_message(TpMessage * self,guint part,const gchar * key,TpMessage * message)698 tp_message_take_message (TpMessage *self,
699                          guint part,
700                          const gchar *key,
701                          TpMessage *message)
702 {
703   g_return_if_fail (TP_IS_CM_MESSAGE (self));
704 
705   tp_cm_message_take_message (self, part, key, message);
706 }
707 
708 static void
subtract_from_hash(gpointer key,gpointer value,gpointer user_data)709 subtract_from_hash (gpointer key,
710                     gpointer value,
711                     gpointer user_data)
712 {
713   DEBUG ("... removing %s", (gchar *) key);
714   g_hash_table_remove (user_data, key);
715 }
716 
717 /**
718  * tp_message_to_text:
719  * @message: a #TpMessage
720  * @out_flags: (out) : if not %NULL, the #TpChannelTextMessageFlags of @message
721  *
722  * Concatene all the text parts contained in @message.
723  *
724  * Returns: (transfer full): a newly allocated string containing the
725  * text content of #message
726  *
727  * Since: 0.13.9
728  */
729 gchar *
tp_message_to_text(TpMessage * message,TpChannelTextMessageFlags * out_flags)730 tp_message_to_text (TpMessage *message,
731     TpChannelTextMessageFlags *out_flags)
732 {
733   guint i;
734   GHashTable *header = g_ptr_array_index (message->parts, 0);
735   /* Lazily created hash tables, used as a sets: keys are borrowed
736    * "alternative" string values from @parts, value == key. */
737   /* Alternative IDs for which we have already extracted an alternative */
738   GHashTable *alternatives_used = NULL;
739   /* Alternative IDs for which we expect to extract text, but have not yet;
740    * cleared if the flag Channel_Text_Message_Flag_Non_Text_Content is set.
741    * At the end, if this contains any item not in alternatives_used,
742    * Channel_Text_Message_Flag_Non_Text_Content must be set. */
743   GHashTable *alternatives_needed = NULL;
744   GString *buffer = g_string_new ("");
745   TpChannelTextMessageFlags flags = 0;
746 
747   if (tp_asv_get_boolean (header, "scrollback", NULL))
748     flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_SCROLLBACK;
749 
750   if (tp_asv_get_boolean (header, "rescued", NULL))
751     flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_RESCUED;
752 
753   /* If the message is on an extended interface, is a delivery report, or only
754    * contains headers, definitely set the "your client is too old" flag. */
755   if (message->parts->len <= 1 ||
756       tp_asv_get_uint32 (header, "message-type", NULL)
757           == TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT ||
758       g_hash_table_lookup (header, "interface") != NULL)
759     {
760       flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT;
761     }
762 
763   for (i = 1; i < message->parts->len; i++)
764     {
765       GHashTable *part = g_ptr_array_index (message->parts, i);
766       const gchar *type = tp_asv_get_string (part, "content-type");
767       const gchar *alternative = tp_asv_get_string (part, "alternative");
768 
769       /* Renamed to "content-type" in spec 0.17.14 */
770       if (type == NULL)
771         type = tp_asv_get_string (part, "type");
772 
773       DEBUG ("Parsing part %u, type %s, alternative %s", i, type, alternative);
774 
775       if (!tp_strdiff (type, "text/plain"))
776         {
777           GValue *value;
778 
779           DEBUG ("... is text/plain");
780 
781           if (alternative != NULL && alternative[0] != '\0')
782             {
783               if (alternatives_used == NULL)
784                 {
785                   /* We can't have seen an alternative for this part yet.
786                    * However, we need to create the hash table now */
787                   alternatives_used = g_hash_table_new (g_str_hash,
788                       g_str_equal);
789                 }
790               else if (g_hash_table_lookup (alternatives_used,
791                     alternative) != NULL)
792                 {
793                   /* we've seen a "better" alternative for this part already.
794                    * Skip it */
795                   DEBUG ("... already saw a better alternative, skipping it");
796                   continue;
797                 }
798 
799               g_hash_table_insert (alternatives_used, (gpointer) alternative,
800                   (gpointer) alternative);
801             }
802 
803           value = g_hash_table_lookup (part, "content");
804 
805           if (value != NULL && G_VALUE_HOLDS_STRING (value))
806             {
807               DEBUG ("... using its text");
808               g_string_append (buffer, g_value_get_string (value));
809 
810               value = g_hash_table_lookup (part, "truncated");
811 
812               if (value != NULL && (!G_VALUE_HOLDS_BOOLEAN (value) ||
813                   g_value_get_boolean (value)))
814                 {
815                   DEBUG ("... appears to have been truncated");
816                   flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_TRUNCATED;
817                 }
818             }
819           else
820             {
821               /* There was a text/plain part we couldn't parse:
822                * that counts as "non-text content" I think */
823               DEBUG ("... didn't understand it, setting NON_TEXT_CONTENT");
824               flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT;
825               tp_clear_pointer (&alternatives_needed, g_hash_table_unref);
826             }
827         }
828       else if ((flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT) == 0)
829         {
830           DEBUG ("... wondering whether this is NON_TEXT_CONTENT?");
831 
832           if (tp_str_empty (alternative))
833             {
834               /* This part can't possibly have a text alternative, since it
835                * isn't part of a multipart/alternative group
836                * (attached image or something, perhaps) */
837               DEBUG ("... ... yes, no possibility of a text alternative");
838               flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT;
839               tp_clear_pointer (&alternatives_needed, g_hash_table_unref);
840             }
841           else if (alternatives_used != NULL &&
842               g_hash_table_lookup (alternatives_used, (gpointer) alternative)
843               != NULL)
844             {
845               DEBUG ("... ... no, we already saw a text alternative");
846             }
847           else
848             {
849               /* This part might have a text alternative later, if we're
850                * lucky */
851               if (alternatives_needed == NULL)
852                 alternatives_needed = g_hash_table_new (g_str_hash,
853                     g_str_equal);
854 
855               DEBUG ("... ... perhaps, but might have text alternative later");
856               g_hash_table_insert (alternatives_needed, (gpointer) alternative,
857                   (gpointer) alternative);
858             }
859         }
860     }
861 
862   if ((flags & TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT) == 0 &&
863       alternatives_needed != NULL)
864     {
865       if (alternatives_used != NULL)
866         g_hash_table_foreach (alternatives_used, subtract_from_hash,
867             alternatives_needed);
868 
869       if (g_hash_table_size (alternatives_needed) > 0)
870         flags |= TP_CHANNEL_TEXT_MESSAGE_FLAG_NON_TEXT_CONTENT;
871     }
872 
873   if (alternatives_needed != NULL)
874     g_hash_table_unref (alternatives_needed);
875 
876   if (alternatives_used != NULL)
877     g_hash_table_unref (alternatives_used);
878 
879   if (out_flags != NULL)
880     {
881       *out_flags = flags;
882     }
883 
884   return g_string_free (buffer, FALSE);
885 }
886 
887 void
_tp_message_set_immutable(TpMessage * self)888 _tp_message_set_immutable (TpMessage *self)
889 {
890   self->priv->mutable = FALSE;
891 }
892 
893 /**
894  * tp_message_is_mutable:
895  * @self: a #TpMessage
896  *
897  * Check if @self is mutable. Only mutable messages can be modified using
898  * functions such as tp_message_set_string().
899  *
900  * Returns: %TRUE if the message is mutable.
901  *
902  * Since: 0.13.9
903  */
904 gboolean
tp_message_is_mutable(TpMessage * self)905 tp_message_is_mutable (TpMessage *self)
906 {
907   g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE);
908 
909   return self->priv->mutable;
910 }
911 
912 /**
913  * tp_message_get_token:
914  * @self: a message
915  *
916  * Return this message's identifier in the underlying protocol. This is
917  * <emphasis>not</emphasis> guaranteed to be unique, even within the scope
918  * of a single channel or contact: the only guarantee made is that two
919  * messages with different non-empty tokens are different messages.
920  *
921  * If there is no suitable token, return %NULL.
922  *
923  * Returns: (transfer none): a non-empty opaque identifier, or %NULL if none
924  *
925  * Since: 0.13.9
926  */
927 const gchar *
tp_message_get_token(TpMessage * self)928 tp_message_get_token (TpMessage *self)
929 {
930   const gchar *token;
931 
932   g_return_val_if_fail (TP_IS_MESSAGE (self), NULL);
933 
934   token = tp_asv_get_string (tp_message_peek (self, 0), "message-token");
935 
936   if (tp_str_empty (token))
937     return NULL;
938   else
939     return token;
940 }
941 
942 /**
943  * tp_message_get_message_type:
944  * @self: a message
945  *
946  * <!-- -->
947  *
948  * Returns: the type of this message
949  *
950  * Since: 0.13.10
951  */
952 TpChannelTextMessageType
tp_message_get_message_type(TpMessage * self)953 tp_message_get_message_type (TpMessage *self)
954 {
955   g_return_val_if_fail (TP_IS_MESSAGE (self),
956       TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL);
957   /* if message-type is absent or invalid we just return 0, which is NORMAL */
958   return tp_asv_get_uint32 (tp_message_peek (self, 0), "message-type", NULL);
959 }
960 
961 /**
962  * tp_message_get_sent_timestamp:
963  * @self: a message
964  *
965  * Return when this message was sent, as a number of seconds since the
966  * beginning of 1970 in the UTC timezone (the same representation used by
967  * g_date_time_new_from_unix_utc(), for instance), or 0 if not known.
968  *
969  * If this protocol does not track the time at which the message was
970  * initially sent, this timestamp might be approximated by using the
971  * time at which it arrived at a central server.
972  *
973  * Returns: a Unix timestamp, or 0
974  *
975  * Since: 0.13.9
976  */
977 gint64
tp_message_get_sent_timestamp(TpMessage * self)978 tp_message_get_sent_timestamp (TpMessage *self)
979 {
980   g_return_val_if_fail (TP_IS_MESSAGE (self), 0);
981   return tp_asv_get_int64 (tp_message_peek (self, 0), "message-sent", NULL);
982 }
983 
984 /**
985  * tp_message_get_received_timestamp:
986  * @self: a message
987  *
988  * Return when this message was received locally, as a number of seconds since
989  * the beginning of 1970 in the UTC timezone (the same representation used by
990  * g_date_time_new_from_unix_utc(), for instance), or 0 if not known.
991  *
992  * Returns: a Unix timestamp, or 0
993  *
994  * Since: 0.13.9
995  */
996 gint64
tp_message_get_received_timestamp(TpMessage * self)997 tp_message_get_received_timestamp (TpMessage *self)
998 {
999   g_return_val_if_fail (TP_IS_MESSAGE (self), 0);
1000   return tp_asv_get_int64 (tp_message_peek (self, 0), "message-received",
1001       NULL);
1002 }
1003 
1004 /**
1005  * tp_message_is_scrollback:
1006  * @self: a message
1007  *
1008  * <!-- no more to say -->
1009  *
1010  * Returns: %TRUE if this message is part of a replay of message history, for
1011  *  instance in an XMPP chatroom.
1012  *
1013  * Since: 0.13.9
1014  */
1015 gboolean
tp_message_is_scrollback(TpMessage * self)1016 tp_message_is_scrollback (TpMessage *self)
1017 {
1018   g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE);
1019   return tp_asv_get_boolean (tp_message_peek (self, 0), "scrollback", NULL);
1020 }
1021 
1022 /**
1023  * tp_message_is_rescued:
1024  * @self: a message
1025  *
1026  * Returns %TRUE if this incoming message has been seen in a previous channel
1027  * during the lifetime of the Connection, but had not been acknowledged when
1028  * that channel closed, causing an identical channel (in which the message now
1029  * appears) to open.
1030  *
1031  * Loggers should check this flag to avoid duplicating messages, for instance.
1032  *
1033  * Returns: %TRUE if this message was seen in a previous Channel on this
1034  *  Connection
1035  *
1036  * Since: 0.13.9
1037  */
1038 gboolean
tp_message_is_rescued(TpMessage * self)1039 tp_message_is_rescued (TpMessage *self)
1040 {
1041   g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE);
1042   return tp_asv_get_boolean (tp_message_peek (self, 0), "rescued", NULL);
1043 }
1044 
1045 /**
1046  * tp_message_get_supersedes:
1047  * @self: a message
1048  *
1049  * If this message replaces a previous message, return the value of
1050  * tp_message_get_token() for that previous message. Otherwise, return %NULL.
1051  *
1052  * For instance, a user interface could replace the superseded
1053  * message with this message, or grey out the superseded message.
1054  *
1055  * Returns: (transfer none): a non-empty opaque identifier, or %NULL if none
1056  *
1057  * Since: 0.13.9
1058  */
1059 const gchar *
tp_message_get_supersedes(TpMessage * self)1060 tp_message_get_supersedes (TpMessage *self)
1061 {
1062   const gchar *token;
1063 
1064   g_return_val_if_fail (TP_IS_MESSAGE (self), NULL);
1065 
1066   token = tp_asv_get_string (tp_message_peek (self, 0), "supersedes");
1067 
1068   if (tp_str_empty (token))
1069     return NULL;
1070   else
1071     return token;
1072 }
1073 
1074 /**
1075  * tp_message_get_specific_to_interface:
1076  * @self: a message
1077  *
1078  * If this message is specific to a particular D-Bus interface and should
1079  * be ignored by clients without knowledge of that interface, return the
1080  * name of the interface.
1081  *
1082  * If this message is an ordinary message or delivery report, return %NULL.
1083  *
1084  * Returns: (transfer none): a D-Bus interface name, or %NULL for ordinary
1085  *  messages and delivery reports
1086  *
1087  * Since: 0.13.9
1088  */
1089 const gchar *
tp_message_get_specific_to_interface(TpMessage * self)1090 tp_message_get_specific_to_interface (TpMessage *self)
1091 {
1092   g_return_val_if_fail (TP_IS_MESSAGE (self), NULL);
1093   return tp_asv_get_string (tp_message_peek (self, 0), "interface");
1094 }
1095 
1096 /**
1097  * tp_message_is_delivery_report:
1098  * @self: a message
1099  *
1100  * If this message is a delivery report indicating success or failure of
1101  * delivering a message, return %TRUE.
1102  *
1103  * Returns: %TRUE if this is a delivery report
1104  *
1105  * Since: 0.13.9
1106  */
1107 gboolean
tp_message_is_delivery_report(TpMessage * self)1108 tp_message_is_delivery_report (TpMessage *self)
1109 {
1110   gboolean valid;
1111 
1112   g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE);
1113 
1114   tp_asv_get_uint32 (tp_message_peek (self, 0), "delivery-status", &valid);
1115   return valid;
1116 }
1117 
1118 /**
1119  * tp_message_get_pending_message_id:
1120  * @self: a message
1121  * @valid: (out): either %NULL, or a location in which to store %TRUE if @self
1122  * contains a pending message ID.
1123  *
1124  * Return the incoming message ID of @self. Only incoming messages have such
1125  * ID, for outgoing ones this function returns 0 and set @valid to %FALSE.
1126  *
1127  * Returns: the incoming message ID.
1128  *
1129  * Since: 0.15.3
1130  */
1131 guint32
tp_message_get_pending_message_id(TpMessage * self,gboolean * valid)1132 tp_message_get_pending_message_id (TpMessage *self,
1133     gboolean *valid)
1134 {
1135   g_return_val_if_fail (TP_IS_MESSAGE (self), FALSE);
1136 
1137   return tp_asv_get_uint32 (tp_message_peek (self, 0),
1138       "pending-message-id", valid);
1139 }
1140 
1141 /*
1142  * Omitted for now:
1143  *
1144  * sender-nickname - perhaps better done in TpSignalledMessage, so we can use
1145  *  the TpContact's nickname if the message doesn't specify?
1146  *
1147  * delivery reporting stuff other than "is this a report?" - later
1148  */
1149