1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2016 Red Hat, Inc. (www.redhat.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 
18 #include "evolution-data-server-config.h"
19 
20 #include <stdio.h>
21 #include <string.h>
22 #include <gio/gio.h>
23 
24 #include "camel-mime-utils.h"
25 #include "camel-utils.h"
26 
27 /**
28  * camel_util_bdata_get_number:
29  * @bdata_ptr: a backend specific data (bdata) pointer
30  * @default_value: a value to return, when no data can be read
31  *
32  * Reads a numeric data from the @bdata_ptr and moves the @bdata_ptr
33  * after that number. If the number cannot be read, then the @default_value
34  * is returned instead and the @bdata_ptr is left unchanged. The number
35  * might be previously stored with the camel_util_bdata_put_number().
36  *
37  * Returns: The read number, or the @default_value, if the @bdata_ptr doesn't
38  *    point to a number.
39  *
40  * Since: 3.24
41  **/
42 gint64
camel_util_bdata_get_number(gchar ** bdata_ptr,gint64 default_value)43 camel_util_bdata_get_number (/* const */ gchar **bdata_ptr,
44 			     gint64 default_value)
45 {
46 	gint64 result;
47 	gchar *endptr;
48 
49 	g_return_val_if_fail (bdata_ptr != NULL, default_value);
50 
51 	if (!bdata_ptr || !*bdata_ptr || !**bdata_ptr)
52 		return default_value;
53 
54 	if (**bdata_ptr == ' ')
55 		*bdata_ptr += 1;
56 
57 	if (!**bdata_ptr)
58 		return default_value;
59 
60 	endptr = *bdata_ptr;
61 
62 	result = g_ascii_strtoll (*bdata_ptr, &endptr, 10);
63 
64 	if (endptr == *bdata_ptr)
65 		result = default_value;
66 	else
67 		*bdata_ptr = endptr;
68 
69 	return result;
70 }
71 
72 /**
73  * camel_util_bdata_put_number:
74  * @bdata_str: a #GString to store a backend specific data (bdata)
75  * @value: a value to store
76  *
77  * Puts the number @value at the end of the @bdata_str. In case the @bdata_str
78  * is not empty a space is added before the numeric @value. The stored value
79  * can be read back with the camel_util_bdata_get_number().
80  *
81  * Since: 3.24
82  **/
83 void
camel_util_bdata_put_number(GString * bdata_str,gint64 value)84 camel_util_bdata_put_number (GString *bdata_str,
85 			     gint64 value)
86 {
87 	g_return_if_fail (bdata_str != NULL);
88 
89 	if (bdata_str->len && bdata_str->str[bdata_str->len - 1] != ' ')
90 		g_string_append_c (bdata_str, ' ');
91 
92 	g_string_append_printf (bdata_str, "%" G_GINT64_FORMAT, value);
93 }
94 
95 /**
96  * camel_util_bdata_get_string:
97  * @bdata_ptr: a backend specific data (bdata) pointer
98  * @default_value: a value to return, when no data can be read
99  *
100  * Reads a string data from the @bdata_ptr and moves the @bdata_ptr
101  * after that string. If the string cannot be read, then the @default_value
102  * is returned instead and the @bdata_ptr is left unchanged. The string
103  * might be previously stored with the camel_util_bdata_put_string().
104  *
105  * Returns: (transfer full): Newly allocated string, which was read, or
106  *    dupped the @default_value, if the @bdata_ptr doesn't point to a string.
107  *    Free returned pointer with g_free() when done with it.
108  *
109  * Since: 3.24
110  **/
111 gchar *
camel_util_bdata_get_string(gchar ** bdata_ptr,const gchar * default_value)112 camel_util_bdata_get_string (/* const */ gchar **bdata_ptr,
113 			     const gchar *default_value)
114 {
115 	gint64 length, has_length;
116 	gchar *orig_bdata_ptr;
117 	gchar *result;
118 
119 	g_return_val_if_fail (bdata_ptr != NULL, NULL);
120 
121 	orig_bdata_ptr = *bdata_ptr;
122 
123 	length = camel_util_bdata_get_number (bdata_ptr, -1);
124 
125 	/* might be a '-' sign */
126 	if (*bdata_ptr && **bdata_ptr == '-')
127 		*bdata_ptr += 1;
128 	else
129 		length = -1;
130 
131 	if (length < 0 || !*bdata_ptr || !**bdata_ptr || *bdata_ptr == orig_bdata_ptr) {
132 		*bdata_ptr = orig_bdata_ptr;
133 
134 		return g_strdup (default_value);
135 	}
136 
137 	if (!length)
138 		return g_strdup ("");
139 
140 	has_length = strlen (*bdata_ptr);
141 	if (has_length < length)
142 		length = has_length;
143 
144 	result = g_strndup (*bdata_ptr, length);
145 	*bdata_ptr += length;
146 
147 	return result;
148 }
149 
150 /**
151  * camel_util_bdata_put_string:
152  * @bdata_str: a #GString to store a backend specific data (bdata)
153  * @value: a value to store
154  *
155  * Puts the string @value at the end of the @bdata_str. In case the @bdata_str
156  * is not empty a space is added before the string @value. The stored value
157  * can be read back with the camel_util_bdata_get_string().
158  *
159  * The strings are encoded as "length-value", quotes for clarity only.
160  *
161  * Since: 3.24
162  **/
163 void
camel_util_bdata_put_string(GString * bdata_str,const gchar * value)164 camel_util_bdata_put_string (GString *bdata_str,
165 			     const gchar *value)
166 {
167 	g_return_if_fail (bdata_str != NULL);
168 	g_return_if_fail (value != NULL);
169 
170 	camel_util_bdata_put_number (bdata_str, strlen (value));
171 
172 	g_string_append_printf (bdata_str, "-%s", value);
173 }
174 
175 /**
176  * camel_time_value_apply:
177  * @src_time: a time_t to apply the value to, or -1 to use the current time
178  * @unit: a #CamelTimeUnit
179  * @value: a value to apply
180  *
181  * Applies the given time @value in unit @unit to the @src_time.
182  * Use negative value to subtract it. The time part is rounded
183  * to the beginning of the day.
184  *
185  * Returns: @src_time modified by the given parameters as date, with
186  *    the time part being beginning of the day.
187  *
188  * Since: 3.24
189  **/
190 time_t
camel_time_value_apply(time_t src_time,CamelTimeUnit unit,gint value)191 camel_time_value_apply (time_t src_time,
192 			CamelTimeUnit unit,
193 			gint value)
194 {
195 	GDate dt;
196 	struct tm tm;
197 
198 	g_return_val_if_fail (unit >= CAMEL_TIME_UNIT_DAYS && unit <= CAMEL_TIME_UNIT_YEARS, src_time);
199 
200 	if (src_time == (time_t) -1)
201 		src_time = time (NULL);
202 
203 	if (!value)
204 		return src_time;
205 
206 	g_date_clear (&dt, 1);
207 
208 	g_date_set_time_t (&dt, src_time);
209 
210 	switch (unit) {
211 	case CAMEL_TIME_UNIT_DAYS:
212 		if (value > 0)
213 			g_date_add_days (&dt, value);
214 		else
215 			g_date_subtract_days (&dt, (-1) * value);
216 		break;
217 	case CAMEL_TIME_UNIT_WEEKS:
218 		if (value > 0)
219 			g_date_add_days (&dt, value * 7);
220 		else
221 			g_date_subtract_days (&dt, (-1) * value * 7);
222 		break;
223 	case CAMEL_TIME_UNIT_MONTHS:
224 		if (value > 0)
225 			g_date_add_months (&dt, value);
226 		else
227 			g_date_subtract_months (&dt, (-1) * value);
228 		break;
229 	case CAMEL_TIME_UNIT_YEARS:
230 		if (value > 0)
231 			g_date_add_years (&dt, value);
232 		else
233 			g_date_subtract_years (&dt, (-1) * value);
234 		break;
235 	}
236 
237 	g_date_to_struct_tm (&dt, &tm);
238 
239 	tm.tm_sec = 0;
240 	tm.tm_min = 0;
241 	tm.tm_hour = 0;
242 
243 	return mktime (&tm);
244 }
245 
246 /**
247  * camel_utils_weak_ref_new: (skip)
248  * @object: (nullable): a #GObject or %NULL
249  *
250  * Allocates a new #GWeakRef and calls g_weak_ref_set() with @object.
251  *
252  * Free the returned #GWeakRef with camel_utils_weak_ref_free().
253  *
254  * Returns: (transfer full): a new #GWeakRef
255  *
256  * Since: 3.40
257  **/
258 GWeakRef *
camel_utils_weak_ref_new(gpointer object)259 camel_utils_weak_ref_new (gpointer object)
260 {
261 	GWeakRef *weak_ref;
262 
263 	/* Based on e_weak_ref_new(). */
264 
265 	weak_ref = g_slice_new0 (GWeakRef);
266 	g_weak_ref_init (weak_ref, object);
267 
268 	return weak_ref;
269 }
270 
271 /**
272  * camel_utils_weak_ref_free: (skip)
273  * @weak_ref: a #GWeakRef
274  *
275  * Frees a #GWeakRef created by camel_utils_weak_ref_new().
276  *
277  * Since: 3.40
278  **/
279 void
camel_utils_weak_ref_free(GWeakRef * weak_ref)280 camel_utils_weak_ref_free (GWeakRef *weak_ref)
281 {
282 	g_return_if_fail (weak_ref != NULL);
283 
284 	/* Based on e_weak_ref_free(). */
285 
286 	g_weak_ref_clear (weak_ref);
287 	g_slice_free (GWeakRef, weak_ref);
288 }
289 
290 G_LOCK_DEFINE_STATIC (mi_user_headers);
291 static GSettings *mi_user_headers_settings = NULL;
292 static gchar **mi_user_headers = NULL;
293 
294 static void
mi_user_headers_settings_changed_cb(GSettings * settings,const gchar * key,gpointer user_data)295 mi_user_headers_settings_changed_cb (GSettings *settings,
296 				     const gchar *key,
297 				     gpointer user_data)
298 {
299 	G_LOCK (mi_user_headers);
300 
301 	if (mi_user_headers_settings) {
302 		gboolean changed;
303 		gchar **strv;
304 		guint ii, jj = 0;
305 
306 		strv = g_settings_get_strv (mi_user_headers_settings, "camel-message-info-user-headers");
307 		changed = (!mi_user_headers && strv && strv[0]) || (mi_user_headers && (!strv || !strv[0]));
308 
309 		if (mi_user_headers && strv && !changed) {
310 			for (ii = 0, jj = 0; strv[ii] && mi_user_headers[jj] && jj < CAMEL_UTILS_MAX_USER_HEADERS; ii++) {
311 				const gchar *name = NULL;
312 
313 				camel_util_decode_user_header_setting (strv[ii], NULL, &name);
314 
315 				if (name && *name) {
316 					if (g_ascii_strcasecmp (mi_user_headers[jj], name) != 0) {
317 						changed = TRUE;
318 						break;
319 					}
320 					jj++;
321 				}
322 			}
323 
324 			changed = changed || (strv[ii] && jj < CAMEL_UTILS_MAX_USER_HEADERS) || (!strv[ii] && jj < CAMEL_UTILS_MAX_USER_HEADERS && mi_user_headers[jj]);
325 		}
326 
327 		if (changed) {
328 			GPtrArray *array;
329 
330 			array = g_ptr_array_sized_new (jj + 2);
331 
332 			for (ii = 0, jj = 0; strv && strv[ii] && jj < CAMEL_UTILS_MAX_USER_HEADERS; ii++) {
333 				const gchar *name = NULL;
334 
335 				camel_util_decode_user_header_setting (strv[ii], NULL, &name);
336 
337 				if (name && *name) {
338 					g_ptr_array_add (array, g_strdup (name));
339 					jj++;
340 				}
341 			}
342 
343 			/* NULL-terminated */
344 			g_ptr_array_add (array, NULL);
345 
346 			g_strfreev (mi_user_headers);
347 			mi_user_headers = (gchar **) g_ptr_array_free (array, FALSE);
348 		}
349 
350 		g_strfreev (strv);
351 	}
352 
353 	G_UNLOCK (mi_user_headers);
354 }
355 
356 /* private functions */
357 void _camel_utils_initialize (void);
358 void _camel_utils_shutdown (void);
359 
360 /* <private> */
361 void
_camel_utils_initialize(void)362 _camel_utils_initialize (void)
363 {
364 	G_LOCK (mi_user_headers);
365 	mi_user_headers_settings = g_settings_new ("org.gnome.evolution-data-server");
366 	g_signal_connect (mi_user_headers_settings, "changed::camel-message-info-user-headers",
367 		G_CALLBACK (mi_user_headers_settings_changed_cb), NULL);
368 	G_UNLOCK (mi_user_headers);
369 	mi_user_headers_settings_changed_cb (NULL, NULL, NULL);
370 }
371 
372 /* <private> */
373 void
_camel_utils_shutdown(void)374 _camel_utils_shutdown (void)
375 {
376 	G_LOCK (mi_user_headers);
377 	if (mi_user_headers_settings) {
378 		g_clear_object (&mi_user_headers_settings);
379 		g_strfreev (mi_user_headers);
380 		mi_user_headers = NULL;
381 	}
382 	G_UNLOCK (mi_user_headers);
383 }
384 
385 /**
386  * camel_util_fill_message_info_user_headers:
387  * @info: a #CamelMessageInfo
388  * @headers: a #CamelNameValueArray with the headers to read from
389  *
390  * Fill @info 's user-headers with the user-defined headers from
391  * the @headers array.
392  *
393  * Returns: Whether the @info's user headers changed
394  *
395  * Since: 3.42
396  **/
397 gboolean
camel_util_fill_message_info_user_headers(CamelMessageInfo * info,const CamelNameValueArray * headers)398 camel_util_fill_message_info_user_headers (CamelMessageInfo *info,
399 					   const CamelNameValueArray *headers)
400 {
401 	gboolean changed = FALSE;
402 
403 	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (info), FALSE);
404 	g_return_val_if_fail (headers != NULL, FALSE);
405 
406 	camel_message_info_freeze_notifications (info);
407 
408 	G_LOCK (mi_user_headers);
409 
410 	if (mi_user_headers) {
411 		CamelNameValueArray *array;
412 		guint ii;
413 
414 		array = camel_name_value_array_new ();
415 
416 		for (ii = 0; mi_user_headers[ii]; ii++) {
417 			const gchar *value;
418 			gchar *str;
419 
420 			value = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, mi_user_headers[ii]);
421 			if (!value)
422 				continue;
423 
424 			while (*value && g_ascii_isspace (*value))
425 				value++;
426 
427 			str = camel_header_unfold (value);
428 
429 			if (str && *str)
430 				camel_name_value_array_set_named (array, CAMEL_COMPARE_CASE_INSENSITIVE, mi_user_headers[ii], str);
431 			else
432 				camel_name_value_array_remove_named (array, CAMEL_COMPARE_CASE_INSENSITIVE, mi_user_headers[ii], TRUE);
433 
434 			g_free (str);
435 		}
436 
437 		if (camel_name_value_array_get_length (array) == 0) {
438 			camel_name_value_array_free (array);
439 			array = NULL;
440 		}
441 
442 		changed = camel_message_info_take_user_headers (info, array);
443 	}
444 
445 	G_UNLOCK (mi_user_headers);
446 
447 	camel_message_info_thaw_notifications (info);
448 
449 	return changed;
450 }
451 
452 /**
453  * camel_util_encode_user_header_setting:
454  * @display_name: (nullable): display name for the header name, or %NULL
455  * @header_name: the header name
456  *
457  * Encode the optional @display_name and the @header_name to a value suitable
458  * for GSettings schema org.gnome.evolution-data-server and key camel-message-info-user-headers.
459  *
460  * Free the returned string with g_free(), when no longer needed.
461  *
462  * Returns: (transfer full): a newly allocated string with encoded @display_name
463  *    and @header_name
464  *
465  * Since: 3.42
466  **/
467 gchar *
camel_util_encode_user_header_setting(const gchar * display_name,const gchar * header_name)468 camel_util_encode_user_header_setting (const gchar *display_name,
469 				       const gchar *header_name)
470 {
471 	g_return_val_if_fail (header_name && *header_name, NULL);
472 
473 	if (display_name && *display_name)
474 		return g_strconcat (display_name, "|", header_name, NULL);
475 
476 	return g_strdup (header_name);
477 }
478 
479 /**
480  * camel_util_decode_user_header_setting:
481  * @setting_value: the value to decode
482  * @out_display_name: (out) (transfer full) (nullable): location for the decoded display name, or %NULL when not needed
483  * @out_header_name: (out): the location for the decoded header name
484  *
485  * Decode the values previously encoded by camel_util_encode_user_header_setting().
486  * The @out_header_name points to the @setting_value, thus it's valid as long
487  * as the @setting_value is valid and unchanged.
488  *
489  * The @out_header_name can result in %NULL when the @setting_value
490  * contains invalid data.
491  *
492  * The @out_display_name can result in %NULL when the @setting_value
493  * does not contain the display name. In such case the header name can
494  * be used as the display name.
495  *
496  * Since: 3.42
497  **/
498 void
camel_util_decode_user_header_setting(const gchar * setting_value,gchar ** out_display_name,const gchar ** out_header_name)499 camel_util_decode_user_header_setting (const gchar *setting_value,
500 				       gchar **out_display_name,
501 				       const gchar **out_header_name)
502 {
503 	const gchar *ptr;
504 
505 	g_return_if_fail (setting_value != NULL);
506 	g_return_if_fail (out_header_name != NULL);
507 
508 	*out_header_name = NULL;
509 
510 	if (out_display_name)
511 		*out_display_name = NULL;
512 
513 	if (!*setting_value)
514 		return;
515 
516 	ptr = strchr (setting_value, '|');
517 
518 	/* Nothing after the pipe means no header name */
519 	if (ptr && !ptr[1])
520 		return;
521 
522 	if (ptr) {
523 		if (out_display_name && ptr != setting_value)
524 			*out_display_name = g_strndup (setting_value, ptr - setting_value);
525 
526 		*out_header_name = ptr + 1;
527 	} else {
528 		*out_header_name = setting_value;
529 	}
530 }
531