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