Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / lib / e2k-properties.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2001-2004 Novell, Inc.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "e2k-properties.h"
25 #include "e2k-propnames.h"
26 #include "e2k-utils.h"
27
28 #include <ctype.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include <libxml/xmlmemory.h>
33
34 struct E2kProperties {
35         GHashTable *set, *removed;
36 };
37
38 typedef struct {
39         char *name;
40         const char *namespace;
41         const char *short_name;
42
43         E2kPropType type;
44         guint32 proptag;
45 } E2kPropInfo;
46
47 static GHashTable *known_properties;
48
49 /**
50  * e2k_properties_new:
51  *
52  * Creates a new (empty) #E2kProperties structure
53  *
54  * Return value: the structure
55  **/
56 E2kProperties *
57 e2k_properties_new (void)
58 {
59         E2kProperties *props;
60
61         props = g_new0 (E2kProperties, 1);
62         props->set = g_hash_table_new (g_str_hash, g_str_equal);
63         props->removed = g_hash_table_new (g_str_hash, g_str_equal);
64
65         return props;
66 }
67
68 static void
69 copy_prop (gpointer key, gpointer value, gpointer data)
70 {
71         const char *name = key;
72         GHashTable *props_copy = data;
73         gpointer value_copy;
74         E2kPropInfo *pi;
75
76         pi = g_hash_table_lookup (known_properties, name);
77         switch (pi->type) {
78         case E2K_PROP_TYPE_BINARY_ARRAY:
79         {
80                 GPtrArray *orig = value, *copy;
81                 GByteArray *new, *old;
82                 int i;
83
84                 copy = g_ptr_array_new ();
85                 for (i = 0; i < orig->len; i++) {
86                         old = orig->pdata[i];
87                         new = g_byte_array_new ();
88                         g_byte_array_append (new, old->data, old->len);
89                         g_ptr_array_add (copy, new);
90                 }
91                 value_copy = copy;
92                 break;
93         }
94
95         case E2K_PROP_TYPE_STRING_ARRAY:
96         {
97                 GPtrArray *orig = value, *copy;
98                 int i;
99
100                 copy = g_ptr_array_new ();
101                 for (i = 0; i < orig->len; i++)
102                         g_ptr_array_add (copy, g_strdup (orig->pdata[i]));
103                 value_copy = copy;
104                 break;
105         }
106
107         case E2K_PROP_TYPE_BINARY:
108         {
109                 GByteArray *orig = value, *copy;
110
111                 copy = g_byte_array_new ();
112                 g_byte_array_append (copy, orig->data, orig->len);
113                 value_copy = copy;
114                 break;
115         }
116
117         case E2K_PROP_TYPE_XML:
118                 value_copy = xmlCopyNode (value, TRUE);
119                 break;
120
121         case E2K_PROP_TYPE_STRING:
122         default:
123                 value_copy = g_strdup (value);
124                 break;
125         }
126
127         g_hash_table_insert (props_copy, pi->name, value_copy);
128 }
129
130 /**
131  * e2k_properties_copy:
132  * @props: an #E2kProperties
133  *
134  * Performs a deep copy of @props
135  *
136  * Return value: a new copy of @props
137  **/
138 E2kProperties *
139 e2k_properties_copy (E2kProperties *props)
140 {
141         E2kProperties *copy;
142
143         g_return_val_if_fail (props != NULL, NULL);
144
145         copy = e2k_properties_new ();
146         g_hash_table_foreach (props->set, copy_prop, copy->set);
147         g_hash_table_foreach (props->removed, copy_prop, copy->removed);
148         return copy;
149 }
150
151 static void
152 free_prop (E2kPropInfo *pi, gpointer value)
153 {
154         if (!value)
155                 return;
156
157         switch (pi->type) {
158         case E2K_PROP_TYPE_BINARY_ARRAY:
159         {
160                 GPtrArray *array = value;
161                 int i;
162
163                 for (i = 0; i < array->len; i++)
164                         g_byte_array_free (array->pdata[i], TRUE);
165                 g_ptr_array_free (array, TRUE);
166                 break;
167         }
168
169         case E2K_PROP_TYPE_STRING_ARRAY:
170         case E2K_PROP_TYPE_INT_ARRAY:
171         {
172                 GPtrArray *array = value;
173                 int i;
174
175                 for (i = 0; i < array->len; i++)
176                         g_free (array->pdata[i]);
177                 g_ptr_array_free (array, TRUE);
178                 break;
179         }
180
181         case E2K_PROP_TYPE_BINARY:
182                 g_byte_array_free (value, TRUE);
183                 break;
184
185         case E2K_PROP_TYPE_XML:
186                 xmlFreeNode (value);
187                 break;
188
189         case E2K_PROP_TYPE_STRING:
190         default:
191                 g_free (value);
192                 break;
193         }
194 }
195
196 static void
197 properties_free_cb (gpointer key, gpointer value, gpointer data)
198 {
199         E2kPropInfo *pi;
200
201         pi = g_hash_table_lookup (known_properties, key);
202         if (pi)
203                 free_prop (pi, value);
204 }
205
206 /**
207  * e2k_properties_free:
208  * @props: an #E2kProperties
209  *
210  * Frees @props and all of the properties it contains.
211  **/
212 void
213 e2k_properties_free (E2kProperties *props)
214 {
215         g_return_if_fail (props != NULL);
216
217         g_hash_table_foreach (props->set, properties_free_cb, NULL);
218         g_hash_table_destroy (props->set);
219         g_hash_table_destroy (props->removed);
220         g_free (props);
221 }
222
223 /**
224  * e2k_properties_get_prop:
225  * @props: an #E2kProperties
226  * @propname: a property name
227  *
228  * Retrieves the value of @propname in @props.
229  *
230  * Return value: the value of @propname in @props, or %NULL if it is
231  * not set. The caller should not free the value; it is owned by
232  * @props.
233  **/
234 gpointer
235 e2k_properties_get_prop (E2kProperties *props, const char *propname)
236 {
237         g_return_val_if_fail (props != NULL, NULL);
238
239         return g_hash_table_lookup (props->set, propname);
240 }
241
242 /**
243  * e2k_properties_empty:
244  * @props: an #E2kProperties
245  *
246  * Tests if @props is empty.
247  *
248  * Return value: %TRUE if @props has no properties set, %FALSE if it
249  * has at least one value set.
250  **/
251 gboolean
252 e2k_properties_empty (E2kProperties *props)
253 {
254         g_return_val_if_fail (props != NULL, TRUE);
255
256         return g_hash_table_size (props->set) == 0;
257 }
258
259
260 extern char e2k_des_key[8];
261
262 static E2kPropInfo *
263 get_propinfo (const char *propname, E2kPropType type)
264 {
265         E2kPropInfo *pi;
266
267         if (!known_properties)
268                 known_properties = g_hash_table_new (g_str_hash, g_str_equal);
269
270         pi = g_hash_table_lookup (known_properties, propname);
271         if (pi) {
272                 if (pi->type == E2K_PROP_TYPE_UNKNOWN)
273                         pi->type = type;
274                 return pi;
275         }
276
277         pi = g_new (E2kPropInfo, 1);
278         pi->name = g_strdup (propname);
279         pi->namespace = e2k_prop_namespace_name (pi->name);
280         pi->short_name = e2k_prop_property_name (pi->name);
281         pi->type = type;
282
283         if (pi->short_name[0] == 'x')
284                 pi->proptag = strtoul (pi->short_name + 1, NULL, 16);
285         else
286                 pi->proptag = 0;
287
288         g_hash_table_insert (known_properties, pi->name, pi);
289
290         return pi;
291 }
292
293 /**
294  * e2k_properties_set_string:
295  * @props: an #E2kProperties
296  * @propname: the name of a property
297  * @value: an allocated string
298  *
299  * Sets @propname in @props to @value. @props assumes ownership of
300  * @value.
301  **/
302
303 /**
304  * e2k_properties_set_string_array:
305  * @props: an #E2kProperties
306  * @propname: the name of a property
307  * @value: an array of allocated strings
308  *
309  * Sets @propname in @props to @value. @props assumes ownership of
310  * @value.
311  **/
312
313 /**
314  * e2k_properties_set_binary:
315  * @props: an #E2kProperties
316  * @propname: the name of a property
317  * @value: a byte array
318  *
319  * Sets @propname in @props to @value. @props assumes ownership of
320  * @value.
321  **/
322
323 /**
324  * e2k_properties_set_binary_array:
325  * @props: an #E2kProperties
326  * @propname: the name of a property
327  * @value: an array of byte arrays
328  *
329  * Sets @propname in @props to @value. @props assumes ownership of
330  * @value.
331  **/
332
333 /**
334  * e2k_properties_set_xml:
335  * @props: an #E2kProperties
336  * @propname: the name of a property
337  * @value: an #xmlNode
338  *
339  * Sets @propname in @props to @value. @props assumes ownership of
340  * @value.
341  **/
342
343 /**
344  * e2k_properties_set_int:
345  * @props: an #E2kProperties
346  * @propname: the name of a property
347  * @value: an integer
348  *
349  * Sets @propname in @props to @value.
350  **/
351
352 /**
353  * e2k_properties_set_int_array:
354  * @props: an #E2kProperties
355  * @propname: the name of a property
356  * @value: an array of integers
357  *
358  * Sets @propname in @props to @value. @props assumes ownership of
359  * @value.
360  **/
361
362 /**
363  * e2k_properties_set_float:
364  * @props: an #E2kProperties
365  * @propname: the name of a property
366  * @value: a floating-point value
367  *
368  * Sets @propname in @props to @value.
369  **/
370
371 /**
372  * e2k_properties_set_bool:
373  * @props: an #E2kProperties
374  * @propname: the name of a property
375  * @value: a boolean value
376  *
377  * Sets @propname in @props to @value.
378  **/
379
380 /**
381  * e2k_properties_set_date:
382  * @props: an #E2kProperties
383  * @propname: the name of a property
384  * @value: an allocated string containing an Exchange timestamp
385  *
386  * Sets @propname in @props to @value. @props assumes ownership of
387  * @value.
388  **/
389
390 #define E2K_PROPERTIES_SETTER(fname, valuetype, pitype, data)           \
391 void                                                                    \
392 e2k_properties_set_ ## fname (E2kProperties *props,                     \
393                               const char    *propname,                  \
394                               valuetype      value)                     \
395 {                                                                       \
396         E2kPropInfo *pi;                                                \
397                                                                         \
398         pi = get_propinfo (propname, E2K_PROP_TYPE_ ## pitype);         \
399         free_prop (pi, g_hash_table_lookup (props->set, pi->name));     \
400         g_hash_table_insert (props->set, pi->name, data);               \
401         g_hash_table_remove (props->removed, pi->name);                 \
402 }
403
404 E2K_PROPERTIES_SETTER (string, char *, STRING, value)
405 E2K_PROPERTIES_SETTER (string_array, GPtrArray *, STRING_ARRAY, value)
406 E2K_PROPERTIES_SETTER (binary, GByteArray *, BINARY, value)
407 E2K_PROPERTIES_SETTER (binary_array, GPtrArray *, BINARY_ARRAY, value)
408 E2K_PROPERTIES_SETTER (xml, xmlNode *, XML, value)
409
410 E2K_PROPERTIES_SETTER (int, int, INT, g_strdup_printf ("%d", value))
411 E2K_PROPERTIES_SETTER (int_array, GPtrArray *, INT_ARRAY, value)
412 E2K_PROPERTIES_SETTER (float, float, FLOAT, g_strdup_printf ("%f", value))
413 E2K_PROPERTIES_SETTER (bool, gboolean, BOOL, g_strdup_printf ("%d", value != FALSE))
414 E2K_PROPERTIES_SETTER (date, char *, DATE, value)
415
416
417
418 /**
419  * e2k_properties_set_type_as_string:
420  * @props: an #E2kProperties
421  * @propname: the name of a property
422  * @type: the type of @value
423  * @value: an allocated string
424  *
425  * Sets @propname in @props to @value, but with type @type. @props
426  * assumes ownership of @value.
427  **/
428
429 /**
430  * e2k_properties_set_type_as_string_array:
431  * @props: an #E2kProperties
432  * @propname: the name of a property
433  * @type: the type of @value
434  * @value: an array of allocated strings
435  *
436  * Sets @propname in @props to @value, but with type @type. @props
437  * assumes ownership of @value.
438  **/
439
440 #define E2K_PROPERTIES_SETTER_AS(fname, valuetype)                      \
441 void                                                                    \
442 e2k_properties_set_type_as_ ## fname (E2kProperties *props,             \
443                                       const char    *propname,          \
444                                       E2kPropType    type,              \
445                                       valuetype      value)             \
446 {                                                                       \
447         E2kPropInfo *pi;                                                \
448                                                                         \
449         pi = get_propinfo (propname, type);                             \
450         free_prop (pi, g_hash_table_lookup (props->set, pi->name));     \
451         g_hash_table_insert (props->set, pi->name, value);              \
452         g_hash_table_remove (props->removed, pi->name);                 \
453 }
454
455 E2K_PROPERTIES_SETTER_AS (string, char *)
456 E2K_PROPERTIES_SETTER_AS (string_array, GPtrArray *)
457
458 /**
459  * e2k_properties_remove:
460  * @props: an #E2kProperties
461  * @propname: the name of a property
462  *
463  * Marks @propname removed in @props, so that the corresponding
464  * property will be removed from the object on the server if @props is
465  * used in a PROPPATCH. If the property was formerly set in @props,
466  * this frees the old value.
467  **/
468 void
469 e2k_properties_remove (E2kProperties *props, const char *propname)
470 {
471         E2kPropInfo *pi;
472
473         pi = get_propinfo (propname, E2K_PROP_TYPE_UNKNOWN);
474         free_prop (pi, g_hash_table_lookup (props->set, pi->name));
475         g_hash_table_remove (props->set, pi->name);
476         g_hash_table_insert (props->removed, pi->name, NULL);
477 }
478
479 struct foreach_data {
480         E2kPropertiesForeachFunc callback;
481         gpointer user_data;
482 };
483
484 static void
485 foreach_callback (gpointer key, gpointer value, gpointer data)
486 {
487         struct foreach_data *fd = data;
488         E2kPropInfo *pi;
489
490         pi = g_hash_table_lookup (known_properties, key);
491         if (pi)
492                 fd->callback (pi->name, pi->type, value, fd->user_data);
493 }
494
495 /**
496  * e2k_properties_foreach:
497  * @props: an #E2kProperties
498  * @callback: callback function to call for each set property
499  * @user_data: data to pass to @callback
500  *
501  * Calls @callback once for each property that is set in @props (in
502  * unspecified order), passing it the name of the property, the
503  * property's type, its value, and @user_data.
504  **/
505 void
506 e2k_properties_foreach (E2kProperties *props,
507                         E2kPropertiesForeachFunc callback,
508                         gpointer user_data)
509 {
510         struct foreach_data fd;
511
512         g_return_if_fail (props != NULL);
513
514         fd.callback = callback;
515         fd.user_data = user_data;
516
517         g_hash_table_foreach (props->set, foreach_callback, &fd);
518 }
519
520 /**
521  * e2k_properties_foreach_removed:
522  * @props: an #E2kProperties
523  * @callback: callback function to call for each set property
524  * @user_data: data to pass to @callback
525  *
526  * Calls @callback once for each property marked removed in @props (in
527  * unspecified order), passing it the name of the property, the
528  * property's type (if known), a %NULL value, and @user_data.
529  **/
530 void
531 e2k_properties_foreach_removed (E2kProperties *props,
532                                 E2kPropertiesForeachFunc callback,
533                                 gpointer user_data)
534 {
535         struct foreach_data fd;
536
537         g_return_if_fail (props != NULL);
538
539         fd.callback = callback;
540         fd.user_data = user_data;
541
542         g_hash_table_foreach (props->removed, foreach_callback, &fd);
543 }
544
545 struct foreach_namespace_data {
546         E2kPropertiesForeachNamespaceFunc callback;
547         gpointer user_data;
548         gboolean need_array_namespace, need_type_namespace;
549         GHashTable *seen_namespaces;
550 };
551
552 static void
553 foreach_namespace_callback (gpointer key, gpointer value, gpointer data)
554 {
555         struct foreach_namespace_data *fnd = data;
556         E2kPropInfo *pi;
557         const char *name;
558
559         pi = g_hash_table_lookup (known_properties, key);
560         if (!pi)
561                 return;
562
563         name = e2k_prop_namespace_name (pi->name);
564         if (!g_hash_table_lookup (fnd->seen_namespaces, name)) {
565                 g_hash_table_insert (fnd->seen_namespaces,
566                                      (char *)name, (char *)name);
567                 fnd->callback (name, e2k_prop_namespace_abbrev (pi->name),
568                                fnd->user_data);
569         }
570
571         switch (pi->type) {
572         case E2K_PROP_TYPE_STRING_ARRAY:
573         case E2K_PROP_TYPE_BINARY_ARRAY:
574         case E2K_PROP_TYPE_INT_ARRAY:
575                 fnd->need_array_namespace = TRUE;
576                 /* fall through */
577
578         case E2K_PROP_TYPE_BINARY:
579         case E2K_PROP_TYPE_INT:
580         case E2K_PROP_TYPE_BOOL:
581         case E2K_PROP_TYPE_FLOAT:
582         case E2K_PROP_TYPE_DATE:
583                 fnd->need_type_namespace = TRUE;
584                 break;
585
586         default:
587                 break;
588         }
589 }
590
591 /**
592  * e2k_properties_foreach_namespace:
593  * @props: an #E2kProperties
594  * @callback: callback function to call for each namespace
595  * @user_data: data to pass to @callback
596  *
597  * Calls @callback once for each unique namespace used by the
598  * properties (set or removed) in @props, passing it the name of the
599  * namespace, its standard abbreviation, and @user_data.
600  **/
601 void
602 e2k_properties_foreach_namespace (E2kProperties *props,
603                                   E2kPropertiesForeachNamespaceFunc callback,
604                                   gpointer user_data)
605 {
606         struct foreach_namespace_data fnd;
607
608         g_return_if_fail (props != NULL);
609
610         fnd.callback = callback;
611         fnd.user_data = user_data;
612         fnd.need_array_namespace = FALSE;
613         fnd.need_type_namespace = FALSE;
614         fnd.seen_namespaces = g_hash_table_new (NULL, NULL);
615
616         g_hash_table_foreach (props->set, foreach_namespace_callback, &fnd);
617         g_hash_table_foreach (props->removed, foreach_namespace_callback, &fnd);
618
619         if (fnd.need_type_namespace)
620                 callback (E2K_NS_TYPE, 'T', user_data);
621         if (fnd.need_array_namespace)
622                 callback ("xml:", 'X', user_data);
623
624         g_hash_table_destroy (fnd.seen_namespaces);
625 }
626
627
628 static GHashTable *namespaces;
629 static int next_namespace = 'a';
630
631
632 static const char *
633 get_div (const char *propname)
634 {
635         const char *div;
636
637         div = strrchr (propname, '/');
638         if (div)
639                 return div;
640         return strrchr (propname, ':');
641 }
642
643 static gint
644 prop_equal (gconstpointer v1, gconstpointer v2)
645 {
646         const char *s1 = (const char *)v1, *s2 = (const char *)v2;
647         const char *d1 = get_div (s1), *d2 = get_div (s2);
648
649         return (d1 - s1 == d2 - s2) && !g_ascii_strncasecmp (s1, s2, d1 - s1);
650 }
651
652 static guint
653 prop_hash (gconstpointer v)
654 {
655         const char *d = get_div (v);
656         const char *p = v;
657         guint h = g_ascii_tolower (*p);
658
659         for (p += 1; p < d; p++)
660                 h = (h << 5) - h + *p;
661         return h;
662 }
663
664 static void
665 setup_namespaces (void)
666 {
667         namespaces = g_hash_table_new (prop_hash, prop_equal);
668         g_hash_table_insert (namespaces, "DAV", GINT_TO_POINTER ('D'));
669 }
670
671 /**
672  * e2k_prop_namespace_name:
673  * @prop: the name of a property
674  *
675  * Splits out the namespace portion of @prop
676  *
677  * Return value: the URI of @prop's namespace
678  **/
679 const char *
680 e2k_prop_namespace_name (const char *prop)
681 {
682         const char *div = get_div (prop);
683         gpointer key, value;
684         char *name;
685
686         if (!namespaces)
687                 setup_namespaces ();
688
689         if (g_hash_table_lookup_extended (namespaces, prop, &key, &value))
690                 return key;
691
692         name = g_strndup (prop, div - prop + 1);
693         g_hash_table_insert (namespaces, name, GINT_TO_POINTER (next_namespace));
694         next_namespace++;
695         return name;
696 }
697
698 /**
699  * e2k_prop_namespace_abbrev:
700  * @prop: the name of a property
701  *
702  * Splits out the namespace portion of @prop and assigns a unique
703  * abbreviation for it.
704  *
705  * Return value: the abbreviation used for prop's namespace
706  **/
707 char
708 e2k_prop_namespace_abbrev (const char *prop)
709 {
710         const char *div = get_div (prop);
711         gpointer key, value;
712         char *name;
713
714         if (!namespaces)
715                 setup_namespaces ();
716
717         if (g_hash_table_lookup_extended (namespaces, prop, &key, &value))
718                 return GPOINTER_TO_INT (value);
719
720         name = g_strndup (prop, div - prop + 1);
721         g_hash_table_insert (namespaces, name, GINT_TO_POINTER (next_namespace));
722         return next_namespace++;
723 }
724
725 /**
726  * e2k_prop_property_name:
727  * @prop: the name of a property
728  *
729  * Splits out the non-namespace portion of @prop
730  *
731  * Return value: the non-namespaced name of @prop
732  **/
733 const char *
734 e2k_prop_property_name (const char *prop)
735 {
736         return get_div (prop) + 1;
737 }
738
739 /**
740  * e2k_prop_proptag:
741  * @prop: the name of a MAPI property
742  *
743  * Computes the MAPI proptag value of @prop, which must be the name
744  * of a MAPI property.
745  * 
746  * Return value: the MAPI proptag value
747  **/
748 guint32
749 e2k_prop_proptag (const char *prop)
750 {
751         E2kPropInfo *pi;
752
753         pi = get_propinfo (prop, E2K_PROP_TYPE_UNKNOWN);
754         return pi->proptag;
755 }
756
757 /**
758  * e2k_proptag_prop:
759  * @proptag: a MAPI property
760  *
761  * Computes the WebDAV property name of the property with the
762  * given proptag.
763  *
764  * Return value: the WebDAV property name associated with @proptag
765  **/
766 const char *
767 e2k_proptag_prop (guint32 proptag)
768 {
769         E2kPropInfo *pi;
770         char *tmpname;
771
772         tmpname = g_strdup_printf (E2K_NS_MAPI_PROPTAG "x%08x",
773                                    (unsigned)proptag);
774
775         pi = get_propinfo (tmpname, E2K_PROP_TYPE_UNKNOWN);
776         g_free (tmpname);
777         return pi->name;
778 }