1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  * This library is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This library is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Michael Zucchi <notzed@ximian.com>
18  */
19 
20 #include "evolution-data-server-config.h"
21 
22 #include <errno.h>
23 #include <stdio.h>
24 #include <string.h>
25 
26 #include <glib/gstdio.h>
27 
28 #include "camel-enums.h"
29 #include "camel-enumtypes.h"
30 #include "camel-file-utils.h"
31 #include "camel-object.h"
32 
33 #define d(x)
34 
35 struct _CamelObjectPrivate {
36 	gchar *state_filename;
37 };
38 
39 enum {
40 	PROP_0,
41 	PROP_STATE_FILENAME
42 };
43 
44 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (CamelObject, camel_object, G_TYPE_OBJECT)
45 
46 /* State file for CamelObject data.
47  * Any later versions should only append data.
48  *
49  * version:uint32
50  *
51  * Version 0 of the file:
52  *
53  * version:uint32 = 0
54  * count:uint32				-- count of meta-data items
55  * ( name:string value:string ) *count		-- meta-data items
56  *
57  * Version 1 of the file adds:
58  * count:uint32					-- count of persistent properties
59  * ( tag:uing32 value:tagtype ) *count		-- persistent properties
60  */
61 
62 #define CAMEL_OBJECT_STATE_FILE_MAGIC "CLMD"
63 
64 /* XXX This is a holdover from Camel's old homegrown type system.
65  *     CamelArg was a kind of primitive version of GObject properties.
66  *     The argument ID and data type were encoded into a 32-bit integer.
67  *     Unfortunately the encoding was also used in the binary state file
68  *     format, so we still need the secret decoder ring. */
69 enum camel_arg_t {
70 	CAMEL_ARG_END = 0,
71 	CAMEL_ARG_IGNORE = 1,	/* override/ignore an arg in-place */
72 
73 	CAMEL_ARG_FIRST = 1024,	/* 1024 args reserved for arg system */
74 
75 	CAMEL_ARG_TYPE = 0xf0000000, /* type field for tags */
76 	CAMEL_ARG_TAG = 0x0fffffff, /* tag field for args */
77 
78 	CAMEL_ARG_OBJ = 0x00000000, /* object */
79 	CAMEL_ARG_INT = 0x10000000, /* gint */
80 	CAMEL_ARG_DBL = 0x20000000, /* gdouble */
81 	CAMEL_ARG_STR = 0x30000000, /* c string */
82 	CAMEL_ARG_PTR = 0x40000000, /* ptr */
83 	CAMEL_ARG_BOO = 0x50000000, /* bool */
84 	CAMEL_ARG_3ST = 0x60000000  /* three-state */
85 };
86 
87 #define CAMEL_ARGV_MAX (20)
88 
89 static void
object_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)90 object_set_property (GObject *object,
91                      guint property_id,
92                      const GValue *value,
93                      GParamSpec *pspec)
94 {
95 	switch (property_id) {
96 		case PROP_STATE_FILENAME:
97 			camel_object_set_state_filename (
98 				CAMEL_OBJECT (object),
99 				g_value_get_string (value));
100 			return;
101 	}
102 
103 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
104 }
105 
106 static void
object_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)107 object_get_property (GObject *object,
108                      guint property_id,
109                      GValue *value,
110                      GParamSpec *pspec)
111 {
112 	switch (property_id) {
113 		case PROP_STATE_FILENAME:
114 			g_value_set_string (
115 				value, camel_object_get_state_filename (
116 				CAMEL_OBJECT (object)));
117 			return;
118 	}
119 
120 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
121 }
122 
123 static void
object_finalize(GObject * object)124 object_finalize (GObject *object)
125 {
126 	CamelObjectPrivate *priv;
127 
128 	priv = CAMEL_OBJECT (object)->priv;
129 
130 	g_free (priv->state_filename);
131 
132 	/* Chain up to parent's finalize() method. */
133 	G_OBJECT_CLASS (camel_object_parent_class)->finalize (object);
134 }
135 
136 static void
object_notify(GObject * object,GParamSpec * pspec)137 object_notify (GObject *object,
138                GParamSpec *pspec)
139 {
140 	/* Placeholder so subclasses can safely chain up, since
141 	 * GObjectClass itself does not implement this method. */
142 }
143 
144 static gint
object_state_read(CamelObject * object,FILE * fp)145 object_state_read (CamelObject *object,
146                    FILE *fp)
147 {
148 	GValue value = G_VALUE_INIT;
149 	GObjectClass *class;
150 	GParamSpec **properties;
151 	guint32 count, version;
152 	guint ii, jj, n_properties;
153 
154 	if (camel_file_util_decode_uint32 (fp, &version) == -1)
155 		return -1;
156 
157 	if (version > 2)
158 		return -1;
159 
160 	if (camel_file_util_decode_uint32 (fp, &count) == -1)
161 		return -1;
162 
163 	/* XXX Camel no longer supports meta-data in state
164 	 *     files, so we're just eating dead data here. */
165 	for (ii = 0; ii < count; ii++) {
166 		gchar *name = NULL;
167 		gchar *value = NULL;
168 		gboolean success;
169 
170 		success =
171 			camel_file_util_decode_string (fp, &name) == 0 &&
172 			camel_file_util_decode_string (fp, &value) == 0;
173 
174 		g_free (name);
175 		g_free (value);
176 
177 		if (!success)
178 			return -1;
179 	}
180 
181 	if (version == 0)
182 		return 0;
183 
184 	if (camel_file_util_decode_uint32 (fp, &count) == -1)
185 		return 0;
186 
187 	if (count == 0 || count > 1024)
188 		/* Maybe it was just version 0 afterall. */
189 		return 0;
190 
191 	count = MIN (count, CAMEL_ARGV_MAX);
192 
193 	class = G_OBJECT_GET_CLASS (object);
194 	properties = g_object_class_list_properties (class, &n_properties);
195 
196 	for (ii = 0; ii < count; ii++) {
197 		gboolean property_set = FALSE;
198 		guint32 tag, v_uint32;
199 		gint32 v_int32;
200 
201 		if (camel_file_util_decode_uint32 (fp, &tag) == -1)
202 			goto exit;
203 
204 		/* Record state file values into GValues.
205 		 * XXX We currently only support booleans and three-state. */
206 		switch (tag & CAMEL_ARG_TYPE) {
207 			case CAMEL_ARG_BOO:
208 				if (camel_file_util_decode_uint32 (fp, &v_uint32) == -1)
209 					goto exit;
210 				g_value_init (&value, G_TYPE_BOOLEAN);
211 				g_value_set_boolean (&value, (gboolean) v_uint32);
212 				break;
213 			case CAMEL_ARG_INT:
214 				if (camel_file_util_decode_fixed_int32 (fp, &v_int32) == -1)
215 					goto exit;
216 				g_value_init (&value, G_TYPE_INT);
217 				g_value_set_int (&value, v_int32);
218 				break;
219 			case CAMEL_ARG_3ST:
220 				if (camel_file_util_decode_uint32 (fp, &v_uint32) == -1)
221 					goto exit;
222 				g_value_init (&value, CAMEL_TYPE_THREE_STATE);
223 				g_value_set_enum (&value, (CamelThreeState) v_uint32);
224 				break;
225 			default:
226 				g_warn_if_reached ();
227 				goto exit;
228 		}
229 
230 		/* Now we have to match the legacy numeric CamelArg tag
231 		 * value with a GObject property.  The GObject property
232 		 * IDs have been set to the same legacy tag values, but
233 		 * we have to access a private GParamSpec field to get
234 		 * to them (pspec->param_id). */
235 
236 		tag &= CAMEL_ARG_TAG;  /* filter out the type code */
237 
238 		for (jj = 0; jj < n_properties; jj++) {
239 			GParamSpec *pspec = properties[jj];
240 
241 			if (pspec->param_id != tag)
242 				continue;
243 
244 			/* Sanity check. */
245 			g_warn_if_fail (pspec->flags & CAMEL_PARAM_PERSISTENT);
246 			if ((pspec->flags & CAMEL_PARAM_PERSISTENT) == 0)
247 				continue;
248 
249 			if (version == 1 && pspec->value_type == CAMEL_TYPE_THREE_STATE &&
250 			    G_VALUE_HOLDS_BOOLEAN (&value)) {
251 				/* Convert from boolean to three-state value. Assign the 'TRUE' to 'On'
252 				   and the rest keep as 'Inconsistent'. */
253 				gboolean stored = g_value_get_boolean (&value);
254 
255 				g_value_unset (&value);
256 				g_value_init (&value, CAMEL_TYPE_THREE_STATE);
257 				g_value_set_enum (&value, stored ? CAMEL_THREE_STATE_ON : CAMEL_THREE_STATE_INCONSISTENT);
258 			}
259 
260 			g_object_set_property (
261 				G_OBJECT (object), pspec->name, &value);
262 
263 			property_set = TRUE;
264 			break;
265 		}
266 
267 		/* XXX This tag was used by the old IMAP backend.
268 		 *     It may still show up in accounts that were
269 		 *     migrated from IMAP to IMAPX.  Silence the
270 		 *     warning. */
271 		if (tag == 0x2500)
272 			property_set = TRUE;
273 
274 		if (!property_set)
275 			g_warning (
276 				"Could not find a corresponding %s "
277 				"property for state file tag 0x%x",
278 				G_OBJECT_TYPE_NAME (object), tag);
279 
280 		g_value_unset (&value);
281 	}
282 
283 exit:
284 	g_free (properties);
285 
286 	return 0;
287 }
288 
289 static gint
object_state_write(CamelObject * object,FILE * fp)290 object_state_write (CamelObject *object,
291                     FILE *fp)
292 {
293 	GValue value = G_VALUE_INIT;
294 	GObjectClass *class;
295 	GParamSpec **properties;
296 	guint ii, n_properties;
297 	guint32 n_persistent = 0;
298 
299 	class = G_OBJECT_GET_CLASS (object);
300 	properties = g_object_class_list_properties (class, &n_properties);
301 
302 	/* Version = 2 */
303 	if (camel_file_util_encode_uint32 (fp, 2) == -1)
304 		goto exit;
305 
306 	/* No meta-data items. */
307 	if (camel_file_util_encode_uint32 (fp, 0) == -1)
308 		goto exit;
309 
310 	/* Count persistent properties. */
311 	for (ii = 0; ii < n_properties; ii++)
312 		if (properties[ii]->flags & CAMEL_PARAM_PERSISTENT)
313 			n_persistent++;
314 
315 	if (camel_file_util_encode_uint32 (fp, n_persistent) == -1)
316 		goto exit;
317 
318 	/* Write a tag + value pair for each persistent property.
319 	 * Tags identify the property ID and data type; they're an
320 	 * artifact of CamelArgs.  The persistent GObject property
321 	 * IDs are set to match the legacy CamelArg tag values. */
322 
323 	for (ii = 0; ii < n_properties; ii++) {
324 		GParamSpec *pspec = properties[ii];
325 		guint32 tag, v_uint32;
326 		gint32 v_int32;
327 
328 		if ((pspec->flags & CAMEL_PARAM_PERSISTENT) == 0)
329 			continue;
330 
331 		g_value_init (&value, pspec->value_type);
332 
333 		g_object_get_property (
334 			G_OBJECT (object), pspec->name, &value);
335 
336 		tag = pspec->param_id;
337 
338 		/* Record the GValue to the state file.
339 		 * XXX We currently only support booleans. */
340 		switch (pspec->value_type) {
341 			case G_TYPE_BOOLEAN:
342 				tag |= CAMEL_ARG_BOO;
343 				v_uint32 = g_value_get_boolean (&value);
344 				if (camel_file_util_encode_uint32 (fp, tag) == -1)
345 					goto exit;
346 				if (camel_file_util_encode_uint32 (fp, v_uint32) == -1)
347 					goto exit;
348 				break;
349 			case G_TYPE_INT:
350 				tag |= CAMEL_ARG_INT;
351 				v_int32 = g_value_get_int (&value);
352 				if (camel_file_util_encode_uint32 (fp, tag) == -1)
353 					goto exit;
354 				if (camel_file_util_encode_fixed_int32 (fp, v_int32) == -1)
355 					goto exit;
356 				break;
357 			default:
358 				if (pspec->value_type == CAMEL_TYPE_THREE_STATE) {
359 					tag |= CAMEL_ARG_3ST;
360 					v_uint32 = g_value_get_enum (&value);
361 					if (camel_file_util_encode_uint32 (fp, tag) == -1)
362 						goto exit;
363 					if (camel_file_util_encode_uint32 (fp, v_uint32) == -1)
364 						goto exit;
365 				} else {
366 					g_warn_if_reached ();
367 					goto exit;
368 				}
369 				break;
370 		}
371 
372 		g_value_unset (&value);
373 	}
374 
375 exit:
376 	g_free (properties);
377 
378 	return 0;
379 }
380 
381 static void
camel_object_class_init(CamelObjectClass * class)382 camel_object_class_init (CamelObjectClass *class)
383 {
384 	GObjectClass *object_class;
385 
386 	object_class = G_OBJECT_CLASS (class);
387 	object_class->set_property = object_set_property;
388 	object_class->get_property = object_get_property;
389 	object_class->finalize = object_finalize;
390 	object_class->notify = object_notify;
391 
392 	class->state_read = object_state_read;
393 	class->state_write = object_state_write;
394 
395 	/**
396 	 * CamelObject:state-filename
397 	 *
398 	 * The file in which to store persistent property values for this
399 	 * instance.
400 	 **/
401 	g_object_class_install_property (
402 		object_class,
403 		PROP_STATE_FILENAME,
404 		g_param_spec_string (
405 			"state-filename",
406 			"State Filename",
407 			"File containing persistent property values",
408 			NULL,
409 			G_PARAM_READWRITE |
410 			G_PARAM_CONSTRUCT |
411 			G_PARAM_EXPLICIT_NOTIFY |
412 			G_PARAM_STATIC_STRINGS));
413 }
414 
415 static void
camel_object_init(CamelObject * object)416 camel_object_init (CamelObject *object)
417 {
418 	object->priv = camel_object_get_instance_private (object);
419 }
420 
421 G_DEFINE_QUARK (camel-error-quark, camel_error)
422 
423 /**
424  * camel_object_state_read:
425  * @object: a #CamelObject
426  *
427  * Read persistent object state from #CamelObject:state-filename.
428  *
429  * Returns: -1 on error.
430  **/
431 gint
camel_object_state_read(CamelObject * object)432 camel_object_state_read (CamelObject *object)
433 {
434 	CamelObjectClass *class;
435 	const gchar *state_filename;
436 	gint res = -1;
437 	FILE *fp;
438 	gchar magic[4];
439 
440 	g_return_val_if_fail (CAMEL_IS_OBJECT (object), -1);
441 
442 	class = CAMEL_OBJECT_GET_CLASS (object);
443 	g_return_val_if_fail (class != NULL, -1);
444 
445 	state_filename = camel_object_get_state_filename (object);
446 	if (state_filename == NULL)
447 		return 0;
448 
449 	fp = g_fopen (state_filename, "rb");
450 	if (fp != NULL) {
451 		if (fread (magic, 4, 1, fp) == 1
452 		    && memcmp (magic, CAMEL_OBJECT_STATE_FILE_MAGIC, 4) == 0)
453 			res = class->state_read (object, fp);
454 		fclose (fp);
455 	}
456 
457 	return res;
458 }
459 
460 /**
461  * camel_object_state_write:
462  * @object: a #CamelObject
463  *
464  * Write persistent object state #CamelObject:state-filename.
465  *
466  * Returns: -1 on error.
467  **/
468 gint
camel_object_state_write(CamelObject * object)469 camel_object_state_write (CamelObject *object)
470 {
471 	CamelObjectClass *class;
472 	const gchar *state_filename;
473 	gchar *savename, *dirname;
474 	gint res = -1;
475 	FILE *fp;
476 
477 	g_return_val_if_fail (CAMEL_IS_OBJECT (object), -1);
478 
479 	class = CAMEL_OBJECT_GET_CLASS (object);
480 	g_return_val_if_fail (class != NULL, -1);
481 
482 	state_filename = camel_object_get_state_filename (object);
483 	if (state_filename == NULL)
484 		return 0;
485 
486 	savename = camel_file_util_savename (state_filename);
487 
488 	dirname = g_path_get_dirname (savename);
489 	g_mkdir_with_parents (dirname, 0700);
490 	g_free (dirname);
491 
492 	fp = g_fopen (savename, "wb");
493 	if (fp != NULL) {
494 		if (fwrite (CAMEL_OBJECT_STATE_FILE_MAGIC, 4, 1, fp) == 1
495 		    && class->state_write (object, fp) == 0) {
496 			if (fclose (fp) == 0) {
497 				res = 0;
498 				if (g_rename (savename, state_filename) == -1)
499 					res = -1;
500 			}
501 		} else {
502 			fclose (fp);
503 		}
504 	} else {
505 		g_warning ("Could not save object state file to '%s': %s", savename, g_strerror (errno));
506 	}
507 
508 	g_free (savename);
509 
510 	return res;
511 }
512 
513 /**
514  * camel_object_get_state_filename:
515  * @object: a #CamelObject
516  *
517  * Returns the name of the file in which persistent property values for
518  * @object are stored.  The file is used by camel_object_state_write()
519  * and camel_object_state_read() to save and restore object state.
520  *
521  * Returns: the name of the persistent property file
522  *
523  * Since: 2.32
524  **/
525 const gchar *
camel_object_get_state_filename(CamelObject * object)526 camel_object_get_state_filename (CamelObject *object)
527 {
528 	g_return_val_if_fail (CAMEL_IS_OBJECT (object), NULL);
529 
530 	return object->priv->state_filename;
531 }
532 
533 /**
534  * camel_object_set_state_filename:
535  * @object: a #CamelObject
536  * @state_filename: path to a local file
537  *
538  * Sets the name of the file in which persistent property values for
539  * @object are stored.  The file is used by camel_object_state_write()
540  * and camel_object_state_read() to save and restore object state.
541  *
542  * Since: 2.32
543  **/
544 void
camel_object_set_state_filename(CamelObject * object,const gchar * state_filename)545 camel_object_set_state_filename (CamelObject *object,
546                                  const gchar *state_filename)
547 {
548 	g_return_if_fail (CAMEL_IS_OBJECT (object));
549 
550 	if (g_strcmp0 (object->priv->state_filename, state_filename) == 0)
551 		return;
552 
553 	g_free (object->priv->state_filename);
554 	object->priv->state_filename = g_strdup (state_filename);
555 
556 	g_object_notify (G_OBJECT (object), "state-filename");
557 }
558