1 /* vi: set et sw=4 ts=4 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /*
4 * This file is part of libaccounts-glib
5 *
6 * Copyright (C) 2009-2010 Nokia Corporation.
7 * Copyright (C) 2012-2016 Canonical Ltd.
8 * Copyright (C) 2012 Intel Corporation.
9 *
10 * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
11 * Contact: Jussi Laako <jussi.laako@linux.intel.com>
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public License
15 * version 2.1 as published by the Free Software Foundation.
16 *
17 * This library is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this library; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
25 * 02110-1301 USA
26 */
27
28 #include "ag-util.h"
29 #include "ag-debug.h"
30 #include "ag-errors.h"
31
32 #include <gio/gio.h>
33 #include <sched.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 GString *
_ag_string_append_printf(GString * string,const gchar * format,...)39 _ag_string_append_printf (GString *string, const gchar *format, ...)
40 {
41 va_list ap;
42 char *sql;
43
44 va_start (ap, format);
45 sql = sqlite3_vmprintf (format, ap);
46 va_end (ap);
47
48 if (sql)
49 {
50 g_string_append (string, sql);
51 sqlite3_free (sql);
52 }
53
54 return string;
55 }
56
57 GValue *
_ag_value_slice_dup(const GValue * value)58 _ag_value_slice_dup (const GValue *value)
59 {
60 GValue *copy;
61
62 if (!value) return NULL;
63 copy = g_slice_new0 (GValue);
64 g_value_init (copy, G_VALUE_TYPE (value));
65 g_value_copy (value, copy);
66 return copy;
67 }
68
69 void
_ag_value_slice_free(GValue * value)70 _ag_value_slice_free (GValue *value)
71 {
72 if (!value) return;
73 g_value_unset (value);
74 g_slice_free (GValue, value);
75 }
76
77 GVariant *
_ag_value_to_variant(const GValue * in_value)78 _ag_value_to_variant (const GValue *in_value)
79 {
80 const GVariantType *type;
81 GValue transformed_value = G_VALUE_INIT;
82 const GValue *value;
83
84 g_return_val_if_fail (in_value != NULL, NULL);
85
86 /* transform some GValues which g_dbus_gvalue_to_gvariant() cannot handle */
87 if (G_VALUE_TYPE (in_value) == G_TYPE_CHAR)
88 {
89 g_value_init (&transformed_value, G_TYPE_INT);
90 if (G_UNLIKELY (!g_value_transform (in_value, &transformed_value)))
91 {
92 g_warning ("%s: could not transform %s to %s", G_STRFUNC,
93 G_VALUE_TYPE_NAME (in_value),
94 G_VALUE_TYPE_NAME (&transformed_value));
95 return NULL;
96 }
97
98 value = &transformed_value;
99 }
100 else
101 {
102 value = in_value;
103 }
104
105 type = _ag_type_from_g_type (G_VALUE_TYPE (value));
106 return g_dbus_gvalue_to_gvariant (value, type);
107 }
108
109 gchar *
_ag_value_to_db(GVariant * value,gboolean type_annotate)110 _ag_value_to_db (GVariant *value, gboolean type_annotate)
111 {
112 return g_variant_print (value, type_annotate);
113 }
114
115 const GVariantType *
_ag_type_from_g_type(GType type)116 _ag_type_from_g_type (GType type)
117 {
118 switch (type)
119 {
120 case G_TYPE_STRING:
121 return G_VARIANT_TYPE_STRING;
122 case G_TYPE_INT:
123 case G_TYPE_CHAR:
124 return G_VARIANT_TYPE_INT32;
125 case G_TYPE_UINT:
126 return G_VARIANT_TYPE_UINT32;
127 case G_TYPE_BOOLEAN:
128 return G_VARIANT_TYPE_BOOLEAN;
129 case G_TYPE_UCHAR:
130 return G_VARIANT_TYPE_BYTE;
131 case G_TYPE_INT64:
132 return G_VARIANT_TYPE_INT64;
133 case G_TYPE_UINT64:
134 return G_VARIANT_TYPE_UINT64;
135 default:
136 /* handle dynamic types here */
137 if (type == G_TYPE_STRV)
138 return G_VARIANT_TYPE_STRING_ARRAY;
139
140 g_warning ("%s: unsupported type ``%s''", G_STRFUNC,
141 g_type_name (type));
142 return NULL;
143 }
144 }
145
146 void
_ag_value_from_variant(GValue * value,GVariant * variant)147 _ag_value_from_variant (GValue *value, GVariant *variant)
148 {
149 g_dbus_gvariant_to_gvalue (variant, value);
150 }
151
152 static GVariant *
_ag_value_from_string(const gchar * type,const gchar * string)153 _ag_value_from_string (const gchar *type, const gchar *string)
154 {
155 GVariant *variant;
156 GError *error = NULL;
157
158 if (G_UNLIKELY (!string)) return NULL;
159
160 /* g_variant_parse() expects all strings to be enclosed in quotes, which we
161 * wouldn't like to enforce in the XML files. So, if we know that we are
162 * reading a string, just build the GValue right away */
163 if (type != NULL && type[0] == 's' && type[1] == '\0' &&
164 string[0] != '"' && string[0] != '\'')
165 {
166 return g_variant_new_string (string);
167 }
168
169 variant = g_variant_parse ((GVariantType *)type, string,
170 NULL, NULL, &error);
171 if (error != 0)
172 {
173 g_warning ("%s: error parsing type \"%s\" ``%s'': %s",
174 G_STRFUNC, type, string, error->message);
175 g_error_free (error);
176 return NULL;
177 }
178
179 return variant;
180 }
181
182 GVariant *
_ag_value_from_db(sqlite3_stmt * stmt,gint col_type,gint col_value)183 _ag_value_from_db (sqlite3_stmt *stmt, gint col_type, gint col_value)
184 {
185 gchar *string_value;
186 gchar *type;
187
188 type = (gchar *)sqlite3_column_text (stmt, col_type);
189 string_value = (gchar *)sqlite3_column_text (stmt, col_value);
190
191 return _ag_value_from_string (type, string_value);
192 }
193
194 /**
195 * ag_errors_quark:
196 *
197 * Return the libaccounts-glib error domain.
198 *
199 * Returns: the libaccounts-glib error domain.
200 */
201 GQuark
ag_errors_quark(void)202 ag_errors_quark (void)
203 {
204 static gsize quark = 0;
205
206 if (g_once_init_enter (&quark))
207 {
208 GQuark domain = g_quark_from_static_string ("ag_errors");
209
210 g_assert (sizeof (GQuark) <= sizeof (gsize));
211
212 g_once_init_leave (&quark, domain);
213 }
214
215 return (GQuark) quark;
216 }
217
218 /**
219 * ag_accounts_error_quark:
220 *
221 * Return the libaccounts-glib error domain.
222 *
223 * Returns: the libaccounts-glib error domain.
224 */
225 GQuark
ag_accounts_error_quark(void)226 ag_accounts_error_quark (void)
227 {
228 return ag_errors_quark ();
229 }
230
231 gboolean
_ag_xml_get_element_data(xmlTextReaderPtr reader,const gchar ** dest_ptr)232 _ag_xml_get_element_data (xmlTextReaderPtr reader, const gchar **dest_ptr)
233 {
234 gint node_type;
235
236 if (dest_ptr) *dest_ptr = NULL;
237
238 if (xmlTextReaderIsEmptyElement (reader))
239 return TRUE;
240
241 if (xmlTextReaderRead (reader) != 1)
242 return FALSE;
243
244 node_type = xmlTextReaderNodeType (reader);
245 if (node_type != XML_READER_TYPE_TEXT)
246 return (node_type == XML_READER_TYPE_END_ELEMENT) ? TRUE : FALSE;
247
248 if (dest_ptr)
249 *dest_ptr = (const gchar *)xmlTextReaderConstValue (reader);
250
251 return TRUE;
252 }
253
254 static gboolean
close_element(xmlTextReaderPtr reader)255 close_element (xmlTextReaderPtr reader)
256 {
257 if (xmlTextReaderRead (reader) != 1 ||
258 xmlTextReaderNodeType (reader) != XML_READER_TYPE_END_ELEMENT)
259 return FALSE;
260
261 return TRUE;
262 }
263
264 gboolean
_ag_xml_dup_element_data(xmlTextReaderPtr reader,gchar ** dest_ptr)265 _ag_xml_dup_element_data (xmlTextReaderPtr reader, gchar **dest_ptr)
266 {
267 const gchar *data;
268 gboolean ret;
269
270 ret = _ag_xml_get_element_data (reader, &data);
271 if (dest_ptr)
272 *dest_ptr = g_strdup (data);
273
274 close_element (reader);
275 return ret;
276 }
277
278 gboolean
_ag_xml_get_boolean(xmlTextReaderPtr reader,gboolean * dest_boolean)279 _ag_xml_get_boolean (xmlTextReaderPtr reader, gboolean *dest_boolean)
280 {
281 GVariant *variant;
282 const gchar *data;
283 gboolean ok;
284
285 ok = _ag_xml_get_element_data (reader, &data);
286 if (G_UNLIKELY (!ok)) return FALSE;
287
288 variant = _ag_value_from_string ("b", data);
289 if (G_UNLIKELY (variant == NULL)) return FALSE;
290
291 *dest_boolean = g_variant_get_boolean (variant);
292 g_variant_unref (variant);
293
294 ok = close_element (reader);
295
296 return ok;
297 }
298
299 static gboolean
parse_param(xmlTextReaderPtr reader,GVariant ** value)300 parse_param (xmlTextReaderPtr reader, GVariant **value)
301 {
302 const gchar *str_value;
303 xmlChar *str_type = NULL;
304 gboolean ok;
305 const gchar *type;
306
307 str_type = xmlTextReaderGetAttribute (reader,
308 (xmlChar *) "type");
309 if (!str_type)
310 type = "s";
311 else
312 {
313 type = (const gchar*)str_type;
314 }
315
316 ok = _ag_xml_get_element_data (reader, &str_value);
317 if (G_UNLIKELY (!ok)) goto error;
318
319 /* Empty value is not an error, but simply ignored */
320 if (G_UNLIKELY (!str_value)) goto finish;
321
322 *value = _ag_value_from_string (type, str_value);
323
324 ok = close_element (reader);
325 if (G_UNLIKELY (!ok))
326 {
327 g_variant_unref (*value);
328 *value = NULL;
329 goto error;
330 }
331
332 finish:
333 ok = TRUE;
334 error:
335 if (str_type != NULL)
336 xmlFree(str_type);
337 return ok;
338 }
339
340 gboolean
_ag_xml_parse_settings(xmlTextReaderPtr reader,const gchar * group,GHashTable * settings)341 _ag_xml_parse_settings (xmlTextReaderPtr reader, const gchar *group,
342 GHashTable *settings)
343 {
344 const gchar *name;
345 int ret, type;
346
347 ret = xmlTextReaderRead (reader);
348 while (ret == 1)
349 {
350 name = (const gchar *)xmlTextReaderConstName (reader);
351 if (G_UNLIKELY (!name)) return FALSE;
352
353 type = xmlTextReaderNodeType (reader);
354 if (type == XML_READER_TYPE_END_ELEMENT)
355 break;
356
357 if (type == XML_READER_TYPE_ELEMENT)
358 {
359 gboolean ok;
360
361 DEBUG_INFO ("found name %s", name);
362 if (strcmp (name, "setting") == 0)
363 {
364 GVariant *value = NULL;
365 xmlChar *key_name;
366 gchar *key;
367
368 key_name = xmlTextReaderGetAttribute (reader, (xmlChar *)"name");
369 key = g_strdup_printf ("%s%s", group, (const gchar*)key_name);
370
371 if (key_name) xmlFree (key_name);
372
373 ok = parse_param (reader, &value);
374 if (ok && value != NULL)
375 {
376 g_variant_take_ref (value);
377 g_hash_table_insert (settings, key, value);
378 }
379 else
380 {
381 if (value != NULL) g_variant_unref (value);
382 g_free (key);
383 }
384 }
385 else if (strcmp (name, "group") == 0 &&
386 xmlTextReaderHasAttributes (reader))
387 {
388 /* it's a subgroup */
389 if (!xmlTextReaderIsEmptyElement (reader))
390 {
391 xmlChar *group_name;
392 gchar *subgroup;
393
394 group_name = xmlTextReaderGetAttribute (reader,
395 (xmlChar *)"name");
396 subgroup = g_strdup_printf ("%s%s/", group,
397 (const gchar *)group_name);
398 if (group_name) xmlFree (group_name);
399
400 ok = _ag_xml_parse_settings (reader, subgroup, settings);
401 g_free (subgroup);
402 }
403 else
404 ok = TRUE;
405 }
406 else
407 {
408 g_warning ("%s: using wrong XML for groups; "
409 "please change to <group name=\"%s\">",
410 xmlTextReaderConstBaseUri (reader), name);
411 /* it's a subgroup */
412 if (!xmlTextReaderIsEmptyElement (reader))
413 {
414 gchar *subgroup;
415
416 subgroup = g_strdup_printf ("%s%s/", group, name);
417 ok = _ag_xml_parse_settings (reader, subgroup, settings);
418 g_free (subgroup);
419 }
420 else
421 ok = TRUE;
422 }
423
424 if (G_UNLIKELY (!ok)) return FALSE;
425 }
426
427 ret = xmlTextReaderNext (reader);
428 }
429 return TRUE;
430 }
431
_ag_xml_parse_element_list(xmlTextReaderPtr reader,const gchar * match,GHashTable ** list)432 gboolean _ag_xml_parse_element_list (xmlTextReaderPtr reader, const gchar *match,
433 GHashTable **list)
434 {
435 gboolean ok = FALSE;
436 const gchar *ename;
437 gchar *data;
438 int res, etype;
439
440 *list = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
441
442 res = xmlTextReaderRead (reader);
443 while (res == 1)
444 {
445 ename = (const gchar *) xmlTextReaderConstName (reader);
446 if (G_UNLIKELY (!ename)) return FALSE;
447
448 etype = xmlTextReaderNodeType (reader);
449 if (etype == XML_READER_TYPE_END_ELEMENT)
450 break;
451
452 if (etype == XML_READER_TYPE_ELEMENT)
453 {
454 if (strcmp (ename, match) == 0)
455 {
456 if (_ag_xml_dup_element_data (reader, &data))
457 {
458 g_hash_table_insert (*list, data, NULL);
459 ok = TRUE;
460 }
461 else return FALSE;
462 }
463 }
464
465 res = xmlTextReaderNext (reader);
466 }
467 return ok;
468 }
469
470 static inline gboolean
_esc_ident_bad(gchar c,gboolean is_first)471 _esc_ident_bad (gchar c, gboolean is_first)
472 {
473 return ((c < 'a' || c > 'z') &&
474 (c < 'A' || c > 'Z') &&
475 (c < '0' || c > '9' || is_first));
476 }
477
478 /**
479 * _ag_dbus_escape_as_identifier:
480 * @name: The string to be escaped
481 *
482 * Taken from telepathy-glib's tp_escape_as_identifier().
483 *
484 * Escape an arbitrary string so it follows the rules for a C identifier,
485 * and hence an object path component, interface element component,
486 * bus name component or member name in D-Bus.
487 *
488 * Unlike g_strcanon this is a reversible encoding, so it preserves
489 * distinctness.
490 *
491 * The escaping consists of replacing all non-alphanumerics, and the first
492 * character if it's a digit, with an underscore and two lower-case hex
493 * digits:
494 *
495 * "0123abc_xyz\x01\xff" -> _30123abc_5fxyz_01_ff
496 *
497 * i.e. similar to URI encoding, but with _ taking the role of %, and a
498 * smaller allowed set. As a special case, "" is escaped to "_" (just for
499 * completeness, really).
500 *
501 * Returns: the escaped string, which must be freed by the caller with #g_free
502 */
503 gchar *
_ag_dbus_escape_as_identifier(const gchar * name)504 _ag_dbus_escape_as_identifier (const gchar *name)
505 {
506 gboolean bad = FALSE;
507 size_t len = 0;
508 GString *op;
509 const gchar *ptr, *first_ok;
510
511 g_return_val_if_fail (name != NULL, NULL);
512
513 /* fast path for empty name */
514 if (name[0] == '\0')
515 return g_strdup ("_");
516
517 for (ptr = name; *ptr; ptr++)
518 {
519 if (_esc_ident_bad (*ptr, ptr == name))
520 {
521 bad = TRUE;
522 len += 3;
523 }
524 else
525 len++;
526 }
527
528 /* fast path if it's clean */
529 if (!bad)
530 return g_strdup (name);
531
532 /* If strictly less than ptr, first_ok is the first uncopied safe
533 * character. */
534 first_ok = name;
535 op = g_string_sized_new (len);
536 for (ptr = name; *ptr; ptr++)
537 {
538 if (_esc_ident_bad (*ptr, ptr == name))
539 {
540 /* copy preceding safe characters if any */
541 if (first_ok < ptr)
542 {
543 g_string_append_len (op, first_ok, ptr - first_ok);
544 }
545 /* escape the unsafe character */
546 g_string_append_printf (op, "_%02x", (unsigned char)(*ptr));
547 /* restart after it */
548 first_ok = ptr + 1;
549 }
550 }
551 /* copy trailing safe characters if any */
552 if (first_ok < ptr)
553 {
554 g_string_append_len (op, first_ok, ptr - first_ok);
555 }
556 return g_string_free (op, FALSE);
557 }
558
559 /**
560 * _ag_find_libaccounts_file:
561 * @file_id: the base name of the file, without suffix.
562 * @suffix: the file suffix.
563 * @env_var: name of the environment variable which could specify an override
564 * path.
565 * @subdir: file will be searched in $XDG_DATA_DIRS/<subdir>/
566 *
567 * Search for the libaccounts file @file_id.
568 *
569 * Returns: the path of the file, if found, %NULL otherwise.
570 */
571 gchar *
_ag_find_libaccounts_file(const gchar * file_id,const gchar * suffix,const gchar * env_var,const gchar * subdir)572 _ag_find_libaccounts_file (const gchar *file_id,
573 const gchar *suffix,
574 const gchar *env_var,
575 const gchar *subdir)
576 {
577 const gchar * const *dirs;
578 const gchar *dirname;
579 const gchar *env_dirname;
580 gchar *filename, *filepath, *desktop_override = NULL;
581
582 filename = g_strconcat (file_id, suffix, NULL);
583 env_dirname = g_getenv (env_var);
584 if (env_dirname)
585 {
586 filepath = g_build_filename (env_dirname, filename, NULL);
587 if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
588 goto found;
589 g_free (filepath);
590 }
591
592 dirname = g_get_user_data_dir ();
593 if (G_LIKELY (dirname))
594 {
595 filepath = g_build_filename (dirname, subdir, filename, NULL);
596 if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
597 goto found;
598 g_free (filepath);
599 }
600
601 /* Check what desktop is this running on */
602 env_dirname = g_getenv ("XDG_CURRENT_DESKTOP");
603 if (env_dirname)
604 desktop_override = g_ascii_strdown (env_dirname, -1);
605
606 dirs = g_get_system_data_dirs ();
607 for (dirname = *dirs; dirname != NULL; dirs++, dirname = *dirs)
608 {
609 /* Check first if desktop override files exist and if yes, load them first */
610 if (desktop_override)
611 {
612 filepath = g_build_filename (dirname, subdir, desktop_override, filename, NULL);
613 if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
614 goto found;
615 g_free (filepath);
616 }
617 filepath = g_build_filename (dirname, subdir, filename, NULL);
618 if (g_file_test (filepath, G_FILE_TEST_IS_REGULAR))
619 goto found;
620 g_free (filepath);
621 }
622
623 filepath = NULL;
624 found:
625 g_free (desktop_override);
626 g_free (filename);
627 return filepath;
628 }
629
630