1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2014-2016 Richard Hughes <richard@hughsie.com>
4 * Copyright (C) 2011 Paolo Bacchilega <paobac@src.gnome.org>
5 *
6 * SPDX-License-Identifier: LGPL-2.1+
7 */
8
9 /**
10 * SECTION:as-utils
11 * @short_description: Helper functions that are used inside libappstream-glib
12 * @include: appstream-glib.h
13 * @stability: Stable
14 *
15 * These functions are used internally to libappstream-glib, and some may be
16 * useful to user-applications.
17 */
18
19 #include "config.h"
20
21 #include <string.h>
22 #include <archive_entry.h>
23 #include <archive.h>
24 #include <libsoup/soup.h>
25 #include <stdlib.h>
26 #ifndef _WIN32
27 #ifdef __APPLE__
28 #include <uuid/uuid.h>
29 #else
30 #include <uuid.h>
31 #endif
32 #endif
33
34 #ifdef HAVE_RPM
35 #include <rpm/rpmlib.h>
36 #endif
37
38 #include "as-app-private.h"
39 #include "as-enums.h"
40 #include "as-node.h"
41 #include "as-resources.h"
42 #include "as-store.h"
43 #include "as-utils.h"
44 #include "as-utils-private.h"
45
46 /**
47 * as_utils_error_quark:
48 *
49 * Return value: An error quark.
50 *
51 * Since: 0.3.7
52 **/
53 G_DEFINE_QUARK (as-utils-error-quark, as_utils_error)
54
55 /**
56 * as_hash_lookup_by_locale:
57 * @hash: a #GHashTable.
58 * @locale: the locale, or %NULL to use the users default local.
59 *
60 * Gets the 'best' data entry in a hash table using the user-set list
61 * of preferred languages.
62 *
63 * This is how methods like as_app_get_name(app,NULL) return the localized
64 * data for the user.
65 *
66 * Returns: the string value, or %NULL if there was no data
67 *
68 * Since: 0.1.0
69 **/
70 const gchar *
as_hash_lookup_by_locale(GHashTable * hash,const gchar * locale)71 as_hash_lookup_by_locale (GHashTable *hash, const gchar *locale)
72 {
73 const gchar *const *locales;
74 const gchar *tmp = NULL;
75 guint i;
76
77 g_return_val_if_fail (hash != NULL, NULL);
78
79 /* the user specified a locale */
80 if (locale != NULL)
81 return g_hash_table_lookup (hash, locale);
82
83 /* use LANGUAGE, LC_ALL, LC_MESSAGES and LANG */
84 locales = g_get_language_names ();
85 for (i = 0; locales[i] != NULL; i++) {
86 tmp = g_hash_table_lookup (hash, locales[i]);
87 if (tmp != NULL)
88 return tmp;
89 }
90 return NULL;
91 }
92
93 static gchar *
as_utils_locale_to_language(const gchar * locale)94 as_utils_locale_to_language (const gchar *locale)
95 {
96 gchar *tmp;
97 gchar *country_code;
98
99 /* invalid */
100 if (locale == NULL)
101 return NULL;
102
103 /* return the part before the _ (not always 2 chars!) */
104 country_code = g_strdup (locale);
105 tmp = g_strstr_len (country_code, -1, "_");
106 if (tmp != NULL)
107 *tmp = '\0';
108 return country_code;
109 }
110
111 /**
112 * as_utils_locale_is_compatible:
113 * @locale1: a locale string, or %NULL
114 * @locale2: a locale string, or %NULL
115 *
116 * Calculates if one locale is compatible with another.
117 * When doing the calculation the locale and language code is taken into
118 * account if possible.
119 *
120 * Returns: %TRUE if the locale is compatible.
121 *
122 * Since: 0.5.14
123 **/
124 gboolean
as_utils_locale_is_compatible(const gchar * locale1,const gchar * locale2)125 as_utils_locale_is_compatible (const gchar *locale1, const gchar *locale2)
126 {
127 g_autofree gchar *lang1 = as_utils_locale_to_language (locale1);
128 g_autofree gchar *lang2 = as_utils_locale_to_language (locale2);
129
130 /* we've specified "don't care" and locale unspecified */
131 if (locale1 == NULL && locale2 == NULL)
132 return TRUE;
133
134 /* forward */
135 if (locale1 == NULL && locale2 != NULL) {
136 const gchar *const *locales = g_get_language_names ();
137 return g_strv_contains (locales, locale2) ||
138 g_strv_contains (locales, lang2);
139 }
140
141 /* backwards */
142 if (locale1 != NULL && locale2 == NULL) {
143 const gchar *const *locales = g_get_language_names ();
144 return g_strv_contains (locales, locale1) ||
145 g_strv_contains (locales, lang1);
146 }
147
148 /* both specified */
149 if (g_strcmp0 (locale1, locale2) == 0)
150 return TRUE;
151 if (g_strcmp0 (locale1, lang2) == 0)
152 return TRUE;
153 if (g_strcmp0 (lang1, locale2) == 0)
154 return TRUE;
155 return FALSE;
156 }
157
158 /**
159 * as_utils_is_stock_icon_name:
160 * @name: an icon name
161 *
162 * Searches the known list of stock icons.
163 *
164 * Returns: %TRUE if the icon is a "stock icon name" and does not need to be
165 * included in the AppStream icon tarball
166 *
167 * Since: 0.1.3
168 **/
169 gboolean
as_utils_is_stock_icon_name(const gchar * name)170 as_utils_is_stock_icon_name (const gchar *name)
171 {
172 g_autoptr(GBytes) data = NULL;
173 g_autofree gchar *key = NULL;
174 gchar *tmp;
175
176 /* load the readonly data section and look for the icon name */
177 data = g_resource_lookup_data (as_get_resource (),
178 "/org/freedesktop/appstream-glib/as-stock-icons.txt",
179 G_RESOURCE_LOOKUP_FLAGS_NONE,
180 NULL);
181 if (data == NULL)
182 return FALSE;
183 key = g_strdup_printf ("\n%s\n", name);
184 tmp = g_strstr_len (key, -1, "-symbolic");
185 if (tmp != NULL) {
186 tmp[0] = '\n';
187 tmp[1] = '\0';
188 }
189 return g_strstr_len (g_bytes_get_data (data, NULL), -1, key) != NULL;
190 }
191
192 /**
193 * as_utils_is_spdx_license_id:
194 * @license_id: a single SPDX license ID, e.g. "CC-BY-3.0"
195 *
196 * Searches the known list of SPDX license IDs.
197 *
198 * Returns: %TRUE if the license ID is a valid "SPDX license ID"
199 *
200 * Since: 0.1.5
201 **/
202 gboolean
as_utils_is_spdx_license_id(const gchar * license_id)203 as_utils_is_spdx_license_id (const gchar *license_id)
204 {
205 g_autoptr(GBytes) data = NULL;
206 g_autofree gchar *key = NULL;
207
208 /* handle invalid */
209 if (license_id == NULL || license_id[0] == '\0')
210 return FALSE;
211
212 /* this is used to map non-SPDX licence-ids to legitimate values */
213 if (g_str_has_prefix (license_id, "LicenseRef-"))
214 return TRUE;
215
216 /* load the readonly data section and look for the license ID */
217 data = g_resource_lookup_data (as_get_resource (),
218 "/org/freedesktop/appstream-glib/as-license-ids.txt",
219 G_RESOURCE_LOOKUP_FLAGS_NONE,
220 NULL);
221 if (data == NULL)
222 return FALSE;
223 key = g_strdup_printf ("\n%s\n", license_id);
224 return g_strstr_len (g_bytes_get_data (data, NULL), -1, key) != NULL;
225 }
226
227 /**
228 * as_utils_is_blacklisted_id:
229 * @desktop_id: a desktop ID, e.g. "gimp.desktop"
230 *
231 * Searches the known list of blacklisted desktop IDs.
232 *
233 * Returns: %TRUE if the desktop ID is blacklisted
234 *
235 * Since: 0.2.2
236 **/
237 gboolean
as_utils_is_blacklisted_id(const gchar * desktop_id)238 as_utils_is_blacklisted_id (const gchar *desktop_id)
239 {
240 return FALSE;
241 }
242
243 /**
244 * as_utils_is_environment_id:
245 * @environment_id: a desktop ID, e.g. "GNOME"
246 *
247 * Searches the known list of registered environment IDs.
248 *
249 * Returns: %TRUE if the environment ID is valid
250 *
251 * Since: 0.2.4
252 **/
253 gboolean
as_utils_is_environment_id(const gchar * environment_id)254 as_utils_is_environment_id (const gchar *environment_id)
255 {
256 g_autoptr(GBytes) data = NULL;
257 g_autofree gchar *key = NULL;
258
259 /* load the readonly data section and look for the environment ID */
260 data = g_resource_lookup_data (as_get_resource (),
261 "/org/freedesktop/appstream-glib/as-environment-ids.txt",
262 G_RESOURCE_LOOKUP_FLAGS_NONE,
263 NULL);
264 if (data == NULL)
265 return FALSE;
266 key = g_strdup_printf ("\n%s\n", environment_id);
267 return g_strstr_len (g_bytes_get_data (data, NULL), -1, key) != NULL;
268 }
269
270 /**
271 * as_utils_is_category_id:
272 * @category_id: a desktop ID, e.g. "AudioVideoEditing"
273 *
274 * Searches the known list of registered category IDs.
275 *
276 * Returns: %TRUE if the category ID is valid
277 *
278 * Since: 0.2.4
279 **/
280 gboolean
as_utils_is_category_id(const gchar * category_id)281 as_utils_is_category_id (const gchar *category_id)
282 {
283 g_autoptr(GBytes) data = NULL;
284 g_autofree gchar *key = NULL;
285
286 /* load the readonly data section and look for the category ID */
287 data = g_resource_lookup_data (as_get_resource (),
288 "/org/freedesktop/appstream-glib/as-category-ids.txt",
289 G_RESOURCE_LOOKUP_FLAGS_NONE,
290 NULL);
291 if (data == NULL)
292 return FALSE;
293 key = g_strdup_printf ("\n%s\n", category_id);
294 return g_strstr_len (g_bytes_get_data (data, NULL), -1, key) != NULL;
295 }
296
297 typedef struct {
298 gboolean last_token_literal;
299 GPtrArray *array;
300 GString *collect;
301 } AsUtilsSpdxHelper;
302
303 static gpointer
_g_ptr_array_last(GPtrArray * array)304 _g_ptr_array_last (GPtrArray *array)
305 {
306 return g_ptr_array_index (array, array->len - 1);
307 }
308
309 static void
as_utils_spdx_license_tokenize_drop(AsUtilsSpdxHelper * helper)310 as_utils_spdx_license_tokenize_drop (AsUtilsSpdxHelper *helper)
311 {
312 const gchar *tmp = helper->collect->str;
313 guint i;
314 g_autofree gchar *last_literal = NULL;
315 struct {
316 const gchar *old;
317 const gchar *new;
318 } licenses[] = {
319 { "CC0", "CC0-1.0" },
320 { "CC-BY", "CC-BY-3.0" },
321 { "CC-BY-SA", "CC-BY-SA-3.0" },
322 { "GFDL", "GFDL-1.3" },
323 { "GPL-2", "GPL-2.0" },
324 { "GPL-3", "GPL-3.0" },
325 { "proprietary", "LicenseRef-proprietary" },
326 { NULL, NULL } };
327
328 /* nothing from last time */
329 if (helper->collect->len == 0)
330 return;
331
332 /* is license enum */
333 if (as_utils_is_spdx_license_id (tmp)) {
334 g_ptr_array_add (helper->array, g_strdup_printf ("@%s", tmp));
335 helper->last_token_literal = FALSE;
336 g_string_truncate (helper->collect, 0);
337 return;
338 }
339
340 /* is license enum with "+" */
341 if (g_str_has_suffix (tmp, "+")) {
342 g_autofree gchar *license_id = g_strndup (tmp, strlen (tmp) - 1);
343 if (as_utils_is_spdx_license_id (license_id)) {
344 g_ptr_array_add (helper->array, g_strdup_printf ("@%s", license_id));
345 g_ptr_array_add (helper->array, g_strdup ("+"));
346 helper->last_token_literal = FALSE;
347 g_string_truncate (helper->collect, 0);
348 return;
349 }
350 }
351
352 /* is old license enum */
353 for (i = 0; licenses[i].old != NULL; i++) {
354 if (g_strcmp0 (tmp, licenses[i].old) != 0)
355 continue;
356 g_ptr_array_add (helper->array,
357 g_strdup_printf ("@%s", licenses[i].new));
358 helper->last_token_literal = FALSE;
359 g_string_truncate (helper->collect, 0);
360 return;
361 }
362
363 /* is conjunctive */
364 if (g_strcmp0 (tmp, "and") == 0 || g_strcmp0 (tmp, "AND") == 0) {
365 g_ptr_array_add (helper->array, g_strdup ("&"));
366 helper->last_token_literal = FALSE;
367 g_string_truncate (helper->collect, 0);
368 return;
369 }
370
371 /* is disjunctive */
372 if (g_strcmp0 (tmp, "or") == 0 || g_strcmp0 (tmp, "OR") == 0) {
373 g_ptr_array_add (helper->array, g_strdup ("|"));
374 helper->last_token_literal = FALSE;
375 g_string_truncate (helper->collect, 0);
376 return;
377 }
378
379 /* is literal */
380 if (helper->last_token_literal) {
381 last_literal = g_strdup (_g_ptr_array_last (helper->array));
382 g_ptr_array_remove_index (helper->array, helper->array->len - 1);
383 g_ptr_array_add (helper->array,
384 g_strdup_printf ("%s %s", last_literal, tmp));
385 } else {
386 g_ptr_array_add (helper->array, g_strdup (tmp));
387 helper->last_token_literal = TRUE;
388 }
389 g_string_truncate (helper->collect, 0);
390 }
391
392 /* SPDX decided to rename some of the really common license IDs in v3
393 * which broke a lot of tools that we cannot really fix now */
394 static GString *
as_utils_spdx_license_3to2(const gchar * license3)395 as_utils_spdx_license_3to2 (const gchar *license3)
396 {
397 GString *license2 = g_string_new (license3);
398 as_utils_string_replace (license2, "-only", "");
399 as_utils_string_replace (license2, "-or-later", "+");
400 return license2;
401 }
402
403 /**
404 * as_utils_spdx_license_tokenize:
405 * @license: a license string, e.g. "LGPLv2+ and (QPL or GPLv2) and MIT"
406 *
407 * Tokenizes the SPDX license string (or any simarly formatted string)
408 * into parts. Any licence parts of the string e.g. "LGPL-2.0+" are prefexed
409 * with "@", the conjunctive replaced with "&" and the disjunctive replaced
410 * with "|". Brackets are added as indervidual tokens and other strings are
411 * appended into single tokens where possible.
412 *
413 * Returns: (transfer full): array of strings, or %NULL for invalid
414 *
415 * Since: 0.1.5
416 **/
417 gchar **
as_utils_spdx_license_tokenize(const gchar * license)418 as_utils_spdx_license_tokenize (const gchar *license)
419 {
420 AsUtilsSpdxHelper helper;
421 g_autoptr(GString) license2 = NULL;
422
423 /* handle invalid */
424 if (license == NULL)
425 return NULL;
426
427 /* SPDX broke the world with v3 */
428 license2 = as_utils_spdx_license_3to2 (license);
429
430 helper.last_token_literal = FALSE;
431 helper.collect = g_string_new ("");
432 helper.array = g_ptr_array_new_with_free_func (g_free);
433 for (guint i = 0; i < license2->len; i++) {
434
435 /* handle brackets */
436 const gchar tmp = license2->str[i];
437 if (tmp == '(' || tmp == ')') {
438 as_utils_spdx_license_tokenize_drop (&helper);
439 g_ptr_array_add (helper.array, g_strdup_printf ("%c", tmp));
440 helper.last_token_literal = FALSE;
441 continue;
442 }
443
444 /* space, so dump queue */
445 if (tmp == ' ') {
446 as_utils_spdx_license_tokenize_drop (&helper);
447 continue;
448 }
449 g_string_append_c (helper.collect, tmp);
450 }
451
452 /* dump anything remaining */
453 as_utils_spdx_license_tokenize_drop (&helper);
454
455 /* return GStrv */
456 g_ptr_array_add (helper.array, NULL);
457 g_string_free (helper.collect, TRUE);
458 return (gchar **) g_ptr_array_free (helper.array, FALSE);
459 }
460
461 /**
462 * as_utils_spdx_license_detokenize:
463 * @license_tokens: license tokens, typically from as_utils_spdx_license_tokenize()
464 *
465 * De-tokenizes the SPDX licenses into a string.
466 *
467 * Returns: (transfer full): string, or %NULL for invalid
468 *
469 * Since: 0.2.5
470 **/
471 gchar *
as_utils_spdx_license_detokenize(gchar ** license_tokens)472 as_utils_spdx_license_detokenize (gchar **license_tokens)
473 {
474 GString *tmp;
475 guint i;
476
477 /* handle invalid */
478 if (license_tokens == NULL)
479 return NULL;
480
481 tmp = g_string_new ("");
482 for (i = 0; license_tokens[i] != NULL; i++) {
483 if (g_strcmp0 (license_tokens[i], "&") == 0) {
484 g_string_append (tmp, " AND ");
485 continue;
486 }
487 if (g_strcmp0 (license_tokens[i], "|") == 0) {
488 g_string_append (tmp, " OR ");
489 continue;
490 }
491 if (g_strcmp0 (license_tokens[i], "+") == 0) {
492 g_string_append (tmp, "+");
493 continue;
494 }
495 if (license_tokens[i][0] != '@') {
496 g_string_append (tmp, license_tokens[i]);
497 continue;
498 }
499 g_string_append (tmp, license_tokens[i] + 1);
500 }
501 return g_string_free (tmp, FALSE);
502 }
503
504 /**
505 * as_utils_is_spdx_license:
506 * @license: a SPDX license string, e.g. "CC-BY-3.0 and GFDL-1.3"
507 *
508 * Checks the licence string to check it being a valid licence.
509 * NOTE: SPDX licences can't typically contain brackets.
510 *
511 * Returns: %TRUE if the license is a valid "SPDX license"
512 *
513 * Since: 0.2.5
514 **/
515 gboolean
as_utils_is_spdx_license(const gchar * license)516 as_utils_is_spdx_license (const gchar *license)
517 {
518 guint i;
519 g_auto(GStrv) tokens = NULL;
520
521 /* handle nothing set */
522 if (license == NULL || license[0] == '\0')
523 return FALSE;
524
525 /* no license information whatsoever */
526 if (g_strcmp0 (license, "NONE") == 0)
527 return TRUE;
528
529 /* creator has intentionally provided no information */
530 if (g_strcmp0 (license, "NOASSERTION") == 0)
531 return TRUE;
532
533 tokens = as_utils_spdx_license_tokenize (license);
534 if (tokens == NULL)
535 return FALSE;
536 for (i = 0; tokens[i] != NULL; i++) {
537 if (tokens[i][0] == '@') {
538 if (as_utils_is_spdx_license_id (tokens[i] + 1))
539 continue;
540 }
541 if (as_utils_is_spdx_license_id (tokens[i]))
542 continue;
543 if (g_strcmp0 (tokens[i], "&") == 0)
544 continue;
545 if (g_strcmp0 (tokens[i], "|") == 0)
546 continue;
547 if (g_strcmp0 (tokens[i], "+") == 0)
548 continue;
549 return FALSE;
550 }
551 return TRUE;
552 }
553
554 /**
555 * as_utils_license_to_spdx:
556 * @license: a not-quite SPDX license string, e.g. "GPLv3+"
557 *
558 * Converts a non-SPDX license into an SPDX format string where possible.
559 *
560 * Returns: the best-effort SPDX license string
561 *
562 * Since: 0.5.5
563 **/
564 gchar *
as_utils_license_to_spdx(const gchar * license)565 as_utils_license_to_spdx (const gchar *license)
566 {
567 GString *str;
568 guint i;
569 guint j;
570 guint license_len;
571 struct {
572 const gchar *old;
573 const gchar *new;
574 } convert[] = {
575 { " with exceptions", NULL },
576 { " with advertising", NULL },
577 { " and ", " AND " },
578 { " or ", " OR " },
579 { "AGPLv3+", "AGPL-3.0" },
580 { "AGPLv3", "AGPL-3.0" },
581 { "Artistic 2.0", "Artistic-2.0" },
582 { "Artistic clarified", "Artistic-2.0" },
583 { "Artistic", "Artistic-1.0" },
584 { "ASL 1.1", "Apache-1.1" },
585 { "ASL 2.0", "Apache-2.0" },
586 { "Boost", "BSL-1.0" },
587 { "BSD", "BSD-3-Clause" },
588 { "CC0", "CC0-1.0" },
589 { "CC-BY-SA", "CC-BY-SA-3.0" },
590 { "CC-BY", "CC-BY-3.0" },
591 { "CDDL", "CDDL-1.0" },
592 { "CeCILL-C", "CECILL-C" },
593 { "CeCILL", "CECILL-2.0" },
594 { "CPAL", "CPAL-1.0" },
595 { "CPL", "CPL-1.0" },
596 { "EPL", "EPL-1.0" },
597 { "Free Art", "ClArtistic" },
598 { "GFDL", "GFDL-1.3" },
599 { "GPL+", "GPL-1.0+" },
600 { "GPLv2+", "GPL-2.0+" },
601 { "GPLv2", "GPL-2.0" },
602 { "GPLv3+", "GPL-3.0+" },
603 { "GPLv3", "GPL-3.0" },
604 { "IBM", "IPL-1.0" },
605 { "LGPL+", "LGPL-2.1+" },
606 { "LGPLv2.1", "LGPL-2.1" },
607 { "LGPLv2+", "LGPL-2.1+" },
608 { "LGPLv2", "LGPL-2.1" },
609 { "LGPLv3+", "LGPL-3.0+" },
610 { "LGPLv3", "LGPL-3.0" },
611 { "LPPL", "LPPL-1.3c" },
612 { "MPLv1.0", "MPL-1.0" },
613 { "MPLv1.1", "MPL-1.1" },
614 { "MPLv2.0", "MPL-2.0" },
615 { "Netscape", "NPL-1.1" },
616 { "OFL", "OFL-1.1" },
617 { "Python", "Python-2.0" },
618 { "QPL", "QPL-1.0" },
619 { "SPL", "SPL-1.0" },
620 { "UPL", "UPL-1.0" },
621 { "zlib", "Zlib" },
622 { "ZPLv2.0", "ZPL-2.0" },
623 { "Unlicense", "CC0-1.0" },
624 { "Public Domain", "LicenseRef-public-domain" },
625 { "SUSE-Public-Domain", "LicenseRef-public-domain" },
626 { "Copyright only", "LicenseRef-public-domain" },
627 { "Proprietary", "LicenseRef-proprietary" },
628 { "Commercial", "LicenseRef-proprietary" },
629 { NULL, NULL } };
630
631 /* nothing set */
632 if (license == NULL)
633 return NULL;
634
635 /* already in SPDX format */
636 if (as_utils_is_spdx_license (license))
637 return g_strdup (license);
638
639 /* go through the string looking for case-insensitive matches */
640 str = g_string_new ("");
641 license_len = (guint) strlen (license);
642 for (i = 0; i < license_len; i++) {
643 gboolean found = FALSE;
644 for (j = 0; convert[j].old != NULL; j++) {
645 guint old_len = (guint) strlen (convert[j].old);
646 if (g_ascii_strncasecmp (license + i,
647 convert[j].old,
648 old_len) != 0)
649 continue;
650 if (convert[j].new != NULL)
651 g_string_append (str, convert[j].new);
652 i += old_len - 1;
653 found = TRUE;
654 }
655 if (!found)
656 g_string_append_c (str, license[i]);
657 }
658 return g_string_free (str, FALSE);
659 }
660
661 static void
as_pixbuf_blur_private(GdkPixbuf * src,GdkPixbuf * dest,gint radius,guchar * div_kernel_size)662 as_pixbuf_blur_private (GdkPixbuf *src, GdkPixbuf *dest, gint radius, guchar *div_kernel_size)
663 {
664 gint width, height, src_rowstride, dest_rowstride, n_channels;
665 guchar *p_src, *p_dest, *c1, *c2;
666 gint x, y, i, i1, i2, width_minus_1, height_minus_1, radius_plus_1;
667 gint r, g, b, a;
668 guchar *p_dest_row, *p_dest_col;
669
670 width = gdk_pixbuf_get_width (src);
671 height = gdk_pixbuf_get_height (src);
672 n_channels = gdk_pixbuf_get_n_channels (src);
673 radius_plus_1 = radius + 1;
674
675 /* horizontal blur */
676 p_src = gdk_pixbuf_get_pixels (src);
677 p_dest = gdk_pixbuf_get_pixels (dest);
678 src_rowstride = gdk_pixbuf_get_rowstride (src);
679 dest_rowstride = gdk_pixbuf_get_rowstride (dest);
680 width_minus_1 = width - 1;
681 for (y = 0; y < height; y++) {
682
683 /* calc the initial sums of the kernel */
684 r = g = b = a = 0;
685 for (i = -radius; i <= radius; i++) {
686 c1 = p_src + (CLAMP (i, 0, width_minus_1) * n_channels);
687 r += c1[0];
688 g += c1[1];
689 b += c1[2];
690 }
691
692 p_dest_row = p_dest;
693 for (x = 0; x < width; x++) {
694 /* set as the mean of the kernel */
695 p_dest_row[0] = div_kernel_size[r];
696 p_dest_row[1] = div_kernel_size[g];
697 p_dest_row[2] = div_kernel_size[b];
698 p_dest_row += n_channels;
699
700 /* the pixel to add to the kernel */
701 i1 = x + radius_plus_1;
702 if (i1 > width_minus_1)
703 i1 = width_minus_1;
704 c1 = p_src + (i1 * n_channels);
705
706 /* the pixel to remove from the kernel */
707 i2 = x - radius;
708 if (i2 < 0)
709 i2 = 0;
710 c2 = p_src + (i2 * n_channels);
711
712 /* calc the new sums of the kernel */
713 r += c1[0] - c2[0];
714 g += c1[1] - c2[1];
715 b += c1[2] - c2[2];
716 }
717
718 p_src += src_rowstride;
719 p_dest += dest_rowstride;
720 }
721
722 /* vertical blur */
723 p_src = gdk_pixbuf_get_pixels (dest);
724 p_dest = gdk_pixbuf_get_pixels (src);
725 src_rowstride = gdk_pixbuf_get_rowstride (dest);
726 dest_rowstride = gdk_pixbuf_get_rowstride (src);
727 height_minus_1 = height - 1;
728 for (x = 0; x < width; x++) {
729
730 /* calc the initial sums of the kernel */
731 r = g = b = a = 0;
732 for (i = -radius; i <= radius; i++) {
733 c1 = p_src + (CLAMP (i, 0, height_minus_1) * src_rowstride);
734 r += c1[0];
735 g += c1[1];
736 b += c1[2];
737 }
738
739 p_dest_col = p_dest;
740 for (y = 0; y < height; y++) {
741 /* set as the mean of the kernel */
742
743 p_dest_col[0] = div_kernel_size[r];
744 p_dest_col[1] = div_kernel_size[g];
745 p_dest_col[2] = div_kernel_size[b];
746 p_dest_col += dest_rowstride;
747
748 /* the pixel to add to the kernel */
749 i1 = y + radius_plus_1;
750 if (i1 > height_minus_1)
751 i1 = height_minus_1;
752 c1 = p_src + (i1 * src_rowstride);
753
754 /* the pixel to remove from the kernel */
755 i2 = y - radius;
756 if (i2 < 0)
757 i2 = 0;
758 c2 = p_src + (i2 * src_rowstride);
759
760 /* calc the new sums of the kernel */
761 r += c1[0] - c2[0];
762 g += c1[1] - c2[1];
763 b += c1[2] - c2[2];
764 }
765
766 p_src += n_channels;
767 p_dest += n_channels;
768 }
769 }
770
771 /**
772 * as_pixbuf_blur:
773 * @src: the GdkPixbuf.
774 * @radius: the pixel radius for the gaussian blur, typical values are 1..3
775 * @iterations: Amount to blur the image, typical values are 1..5
776 *
777 * Blurs an image. Warning, this method is s..l..o..w... for large images.
778 *
779 * Since: 0.3.2
780 **/
781 void
as_pixbuf_blur(GdkPixbuf * src,gint radius,gint iterations)782 as_pixbuf_blur (GdkPixbuf *src, gint radius, gint iterations)
783 {
784 gint kernel_size;
785 gint i;
786 g_autofree guchar *div_kernel_size = NULL;
787 g_autoptr(GdkPixbuf) tmp = NULL;
788
789 tmp = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src),
790 gdk_pixbuf_get_has_alpha (src),
791 gdk_pixbuf_get_bits_per_sample (src),
792 gdk_pixbuf_get_width (src),
793 gdk_pixbuf_get_height (src));
794 kernel_size = 2 * radius + 1;
795 div_kernel_size = g_new (guchar, 256 * kernel_size);
796 for (i = 0; i < 256 * kernel_size; i++)
797 div_kernel_size[i] = (guchar) (i / kernel_size);
798
799 while (iterations-- > 0)
800 as_pixbuf_blur_private (src, tmp, radius, div_kernel_size);
801 }
802
803 #define interpolate_value(original, reference, distance) \
804 (CLAMP (((distance) * (reference)) + \
805 ((1.0 - (distance)) * (original)), 0, 255))
806
807 /**
808 * as_pixbuf_sharpen: (skip)
809 * @src: the GdkPixbuf.
810 * @radius: the pixel radius for the unsharp mask, typical values are 1..3
811 * @amount: Amount to sharpen the image, typical values are -0.1 to -0.9
812 *
813 * Sharpens an image. Warning, this method is s..l..o..w... for large images.
814 *
815 * Since: 0.2.2
816 **/
817 void
as_pixbuf_sharpen(GdkPixbuf * src,gint radius,gdouble amount)818 as_pixbuf_sharpen (GdkPixbuf *src, gint radius, gdouble amount)
819 {
820 gint width, height, rowstride, n_channels;
821 gint x, y;
822 guchar *p_blurred;
823 guchar *p_blurred_row;
824 guchar *p_src;
825 guchar *p_src_row;
826 g_autoptr(GdkPixbuf) blurred = NULL;
827
828 blurred = gdk_pixbuf_copy (src);
829 as_pixbuf_blur (blurred, radius, 3);
830
831 width = gdk_pixbuf_get_width (src);
832 height = gdk_pixbuf_get_height (src);
833 rowstride = gdk_pixbuf_get_rowstride (src);
834 n_channels = gdk_pixbuf_get_n_channels (src);
835
836 p_src = gdk_pixbuf_get_pixels (src);
837 p_blurred = gdk_pixbuf_get_pixels (blurred);
838
839 for (y = 0; y < height; y++) {
840 p_src_row = p_src;
841 p_blurred_row = p_blurred;
842 for (x = 0; x < width; x++) {
843 p_src_row[0] = (guchar) interpolate_value (p_src_row[0],
844 p_blurred_row[0],
845 amount);
846 p_src_row[1] = (guchar) interpolate_value (p_src_row[1],
847 p_blurred_row[1],
848 amount);
849 p_src_row[2] = (guchar) interpolate_value (p_src_row[2],
850 p_blurred_row[2],
851 amount);
852 p_src_row += n_channels;
853 p_blurred_row += n_channels;
854 }
855 p_src += rowstride;
856 p_blurred += rowstride;
857 }
858 }
859
860 /**
861 * as_utils_find_icon_filename_full:
862 * @destdir: the destdir.
863 * @search: the icon search name, e.g. "microphone.svg"
864 * @flags: A #AsUtilsFindIconFlag bitfield
865 * @error: A #GError or %NULL
866 *
867 * Finds an icon filename from a filesystem root.
868 *
869 * Returns: (transfer full): a newly allocated %NULL terminated string
870 *
871 * Since: 0.3.1
872 **/
873 gchar *
as_utils_find_icon_filename_full(const gchar * destdir,const gchar * search,AsUtilsFindIconFlag flags,GError ** error)874 as_utils_find_icon_filename_full (const gchar *destdir,
875 const gchar *search,
876 AsUtilsFindIconFlag flags,
877 GError **error)
878 {
879 guint i;
880 guint j;
881 guint k;
882 guint m;
883 const gchar **sizes;
884 const gchar *pixmap_dirs[] = { "pixmaps", "icons", NULL };
885 const gchar *theme_dirs[] = { "hicolor", "oxygen", NULL };
886 const gchar *supported_ext[] = { ".png",
887 ".gif",
888 ".svg",
889 ".xpm",
890 "",
891 NULL };
892 const gchar *sizes_lo_dpi[] = { "64x64",
893 "128x128",
894 "96x96",
895 "256x256",
896 "512x512",
897 "scalable",
898 "48x48",
899 "32x32",
900 "24x24",
901 "16x16",
902 NULL };
903 const gchar *sizes_hi_dpi[] = { "128x128",
904 "256x256",
905 "512x512",
906 "scalable",
907 NULL };
908 const gchar *types[] = { "actions",
909 "animations",
910 "apps",
911 "categories",
912 "devices",
913 "emblems",
914 "emotes",
915 "filesystems",
916 "intl",
917 "mimetypes",
918 "places",
919 "status",
920 "stock",
921 NULL };
922 g_autofree gchar *prefix = NULL;
923
924 g_return_val_if_fail (search != NULL, NULL);
925
926 /* fallback */
927 if (destdir == NULL)
928 destdir = "";
929
930 /* is this an absolute path */
931 if (search[0] == '/') {
932 g_autofree gchar *tmp = NULL;
933 tmp = g_build_filename (destdir, search, NULL);
934 if (!g_file_test (tmp, G_FILE_TEST_EXISTS)) {
935 g_set_error (error,
936 AS_UTILS_ERROR,
937 AS_UTILS_ERROR_FAILED,
938 "specified icon '%s' does not exist",
939 search);
940 return NULL;
941 }
942 return g_strdup (tmp);
943 }
944
945 /* all now found in the prefix */
946 prefix = g_strdup_printf ("%s/usr", destdir);
947 if (!g_file_test (prefix, G_FILE_TEST_EXISTS)) {
948 g_free (prefix);
949 prefix = g_strdup (destdir);
950 }
951 if (!g_file_test (prefix, G_FILE_TEST_EXISTS)) {
952 g_set_error (error,
953 AS_UTILS_ERROR,
954 AS_UTILS_ERROR_FAILED,
955 "Failed to find icon in prefix %s", search);
956 return NULL;
957 }
958
959 /* icon theme apps */
960 sizes = flags & AS_UTILS_FIND_ICON_HI_DPI ? sizes_hi_dpi : sizes_lo_dpi;
961 for (k = 0; theme_dirs[k] != NULL; k++) {
962 for (i = 0; sizes[i] != NULL; i++) {
963 for (m = 0; types[m] != NULL; m++) {
964 for (j = 0; supported_ext[j] != NULL; j++) {
965 g_autofree gchar *tmp = NULL;
966 tmp = g_strdup_printf ("%s/share/icons/"
967 "%s/%s/%s/%s%s",
968 prefix,
969 theme_dirs[k],
970 sizes[i],
971 types[m],
972 search,
973 supported_ext[j]);
974 if (g_file_test (tmp, G_FILE_TEST_EXISTS))
975 return g_strdup (tmp);
976 }
977 }
978 }
979 }
980
981 /* pixmap */
982 for (i = 0; pixmap_dirs[i] != NULL; i++) {
983 for (j = 0; supported_ext[j] != NULL; j++) {
984 g_autofree gchar *tmp = NULL;
985 tmp = g_strdup_printf ("%s/share/%s/%s%s",
986 prefix,
987 pixmap_dirs[i],
988 search,
989 supported_ext[j]);
990 if (g_file_test (tmp, G_FILE_TEST_IS_REGULAR))
991 return g_strdup (tmp);
992 }
993 }
994
995 /* failed */
996 g_set_error (error,
997 AS_UTILS_ERROR,
998 AS_UTILS_ERROR_FAILED,
999 "Failed to find icon %s", search);
1000 return NULL;
1001 }
1002
1003 /**
1004 * as_utils_find_icon_filename:
1005 * @destdir: the destdir.
1006 * @search: the icon search name, e.g. "microphone.svg"
1007 * @error: A #GError or %NULL
1008 *
1009 * Finds an icon filename from a filesystem root.
1010 *
1011 * Returns: (transfer full): a newly allocated %NULL terminated string
1012 *
1013 * Since: 0.2.5
1014 **/
1015 gchar *
as_utils_find_icon_filename(const gchar * destdir,const gchar * search,GError ** error)1016 as_utils_find_icon_filename (const gchar *destdir,
1017 const gchar *search,
1018 GError **error)
1019 {
1020 return as_utils_find_icon_filename_full (destdir, search,
1021 AS_UTILS_FIND_ICON_NONE,
1022 error);
1023 }
1024
1025 static const gchar *
as_utils_location_get_prefix(AsUtilsLocation location)1026 as_utils_location_get_prefix (AsUtilsLocation location)
1027 {
1028 if (location == AS_UTILS_LOCATION_SHARED)
1029 return "/usr/share";
1030 if (location == AS_UTILS_LOCATION_CACHE)
1031 return "/var/cache";
1032 if (location == AS_UTILS_LOCATION_USER)
1033 return "~/.local/share";
1034 return NULL;
1035 }
1036
1037 static gboolean
as_utils_install_icon(AsUtilsLocation location,const gchar * filename,const gchar * origin,const gchar * destdir,GError ** error)1038 as_utils_install_icon (AsUtilsLocation location,
1039 const gchar *filename,
1040 const gchar *origin,
1041 const gchar *destdir,
1042 GError **error)
1043 {
1044 const gchar *pathname;
1045 const gchar *tmp;
1046 gboolean ret = TRUE;
1047 gsize len;
1048 int r;
1049 struct archive *arch = NULL;
1050 struct archive_entry *entry;
1051 g_autofree gchar *data = NULL;
1052 g_autofree gchar *dir = NULL;
1053
1054 dir = g_strdup_printf ("%s%s/app-info/icons/%s",
1055 destdir,
1056 as_utils_location_get_prefix (location),
1057 origin);
1058
1059 /* load file at once to avoid seeking */
1060 ret = g_file_get_contents (filename, &data, &len, error);
1061 if (!ret)
1062 goto out;
1063
1064 /* read anything */
1065 arch = archive_read_new ();
1066 archive_read_support_format_all (arch);
1067 archive_read_support_filter_all (arch);
1068 r = archive_read_open_memory (arch, data, len);
1069 if (r) {
1070 ret = FALSE;
1071 g_set_error (error,
1072 AS_UTILS_ERROR,
1073 AS_UTILS_ERROR_FAILED,
1074 "Cannot open %s: %s",
1075 filename,
1076 archive_error_string (arch));
1077 goto out;
1078 }
1079
1080 /* decompress each file */
1081 for (;;) {
1082 g_autofree gchar *buf = NULL;
1083
1084 r = archive_read_next_header (arch, &entry);
1085 if (r == ARCHIVE_EOF)
1086 break;
1087 if (r != ARCHIVE_OK) {
1088 ret = FALSE;
1089 g_set_error (error,
1090 AS_UTILS_ERROR,
1091 AS_UTILS_ERROR_FAILED,
1092 "Cannot read header: %s",
1093 archive_error_string (arch));
1094 goto out;
1095 }
1096
1097 /* no output file */
1098 pathname = archive_entry_pathname (entry);
1099 if (pathname == NULL)
1100 continue;
1101
1102 /* update output path */
1103 buf = g_build_filename (dir, pathname, NULL);
1104 archive_entry_update_pathname_utf8 (entry, buf);
1105
1106 /* update hardlinks */
1107 tmp = archive_entry_hardlink (entry);
1108 if (tmp != NULL) {
1109 g_autofree gchar *buf_link = NULL;
1110 buf_link = g_build_filename (dir, tmp, NULL);
1111 archive_entry_update_hardlink_utf8 (entry, buf_link);
1112 }
1113
1114 /* update symlinks */
1115 tmp = archive_entry_symlink (entry);
1116 if (tmp != NULL) {
1117 g_autofree gchar *buf_link = NULL;
1118 buf_link = g_build_filename (dir, tmp, NULL);
1119 archive_entry_update_symlink_utf8 (entry, buf_link);
1120 }
1121
1122 r = archive_read_extract (arch, entry, 0);
1123 if (r != ARCHIVE_OK) {
1124 ret = FALSE;
1125 g_set_error (error,
1126 AS_UTILS_ERROR,
1127 AS_UTILS_ERROR_FAILED,
1128 "Cannot extract: %s",
1129 archive_error_string (arch));
1130 goto out;
1131 }
1132 }
1133 out:
1134 if (arch != NULL) {
1135 archive_read_close (arch);
1136 archive_read_free (arch);
1137 }
1138 return ret;
1139 }
1140
1141 static gboolean
as_utils_install_xml(const gchar * filename,const gchar * origin,const gchar * dir,const gchar * destdir,GError ** error)1142 as_utils_install_xml (const gchar *filename,
1143 const gchar *origin,
1144 const gchar *dir,
1145 const gchar *destdir,
1146 GError **error)
1147 {
1148 gchar *tmp;
1149 g_autofree gchar *basename = NULL;
1150 g_autofree gchar *path_dest = NULL;
1151 g_autofree gchar *path_parent = NULL;
1152 g_autoptr(GFile) file_dest = NULL;
1153 g_autoptr(GFile) file_src = NULL;
1154
1155 /* create directory structure */
1156 path_parent = g_strdup_printf ("%s%s", destdir, dir);
1157 if (g_mkdir_with_parents (path_parent, 0777) != 0) {
1158 g_set_error (error,
1159 AS_UTILS_ERROR,
1160 AS_UTILS_ERROR_FAILED,
1161 "Failed to create %s", path_parent);
1162 return FALSE;
1163 }
1164
1165 /* calculate the new destination */
1166 file_src = g_file_new_for_path (filename);
1167 basename = g_path_get_basename (filename);
1168 if (origin != NULL) {
1169 g_autofree gchar *basename_new = NULL;
1170 tmp = g_strstr_len (basename, -1, ".");
1171 if (tmp == NULL) {
1172 g_set_error (error,
1173 AS_UTILS_ERROR,
1174 AS_UTILS_ERROR_FAILED,
1175 "Name of XML file invalid %s",
1176 basename);
1177 return FALSE;
1178 }
1179 basename_new = g_strdup_printf ("%s%s", origin, tmp);
1180 /* replace the fedora.xml.gz into %{origin}.xml.gz */
1181 path_dest = g_build_filename (path_parent, basename_new, NULL);
1182 } else {
1183 path_dest = g_build_filename (path_parent, basename, NULL);
1184 }
1185
1186 /* actually copy file */
1187 file_dest = g_file_new_for_path (path_dest);
1188 if (!g_file_copy (file_src, file_dest,
1189 G_FILE_COPY_OVERWRITE,
1190 NULL, NULL, NULL, error))
1191 return FALSE;
1192
1193 /* fix the origin */
1194 if (origin != NULL) {
1195 g_autoptr(AsStore) store = NULL;
1196 store = as_store_new ();
1197 if (!as_store_from_file (store, file_dest, NULL, NULL, error))
1198 return FALSE;
1199 as_store_set_origin (store, origin);
1200 if (!as_store_to_file (store, file_dest,
1201 AS_NODE_TO_XML_FLAG_ADD_HEADER |
1202 AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE,
1203 NULL, error))
1204 return FALSE;
1205 }
1206 return TRUE;
1207 }
1208
1209 /**
1210 * as_utils_install_filename:
1211 * @location: the #AsUtilsLocation, e.g. %AS_UTILS_LOCATION_CACHE
1212 * @filename: the full path of the file to install
1213 * @origin: the origin to use for the installation, or %NULL
1214 * @destdir: the destdir to use, or %NULL
1215 * @error: A #GError or %NULL
1216 *
1217 * Installs an AppData, MetaInfo, AppStream XML or AppStream Icon metadata file.
1218 *
1219 * Returns: %TRUE for success, %FALSE if error is set
1220 *
1221 * Since: 0.3.4
1222 **/
1223 gboolean
as_utils_install_filename(AsUtilsLocation location,const gchar * filename,const gchar * origin,const gchar * destdir,GError ** error)1224 as_utils_install_filename (AsUtilsLocation location,
1225 const gchar *filename,
1226 const gchar *origin,
1227 const gchar *destdir,
1228 GError **error)
1229 {
1230 gboolean ret = FALSE;
1231 gchar *tmp;
1232 g_autofree gchar *basename = NULL;
1233 g_autofree gchar *path = NULL;
1234
1235 /* default value */
1236 if (destdir == NULL)
1237 destdir = "";
1238
1239 switch (as_format_guess_kind (filename)) {
1240 case AS_FORMAT_KIND_APPSTREAM:
1241 if (g_strstr_len (filename, -1, ".yml.gz") != NULL) {
1242 path = g_build_filename (as_utils_location_get_prefix (location),
1243 "app-info", "yaml", NULL);
1244 ret = as_utils_install_xml (filename, origin, path, destdir, error);
1245 } else {
1246 path = g_build_filename (as_utils_location_get_prefix (location),
1247 "app-info", "xmls", NULL);
1248 ret = as_utils_install_xml (filename, origin, path, destdir, error);
1249 }
1250 break;
1251 case AS_FORMAT_KIND_APPDATA:
1252 case AS_FORMAT_KIND_METAINFO:
1253 if (location == AS_UTILS_LOCATION_CACHE) {
1254 g_set_error_literal (error,
1255 AS_UTILS_ERROR,
1256 AS_UTILS_ERROR_INVALID_TYPE,
1257 "cached location unsupported for "
1258 "MetaInfo and AppData files");
1259 return FALSE;
1260 }
1261 path = g_build_filename (as_utils_location_get_prefix (location),
1262 "appdata", NULL);
1263 ret = as_utils_install_xml (filename, NULL, path, destdir, error);
1264 break;
1265 default:
1266 /* icons */
1267 if (origin != NULL) {
1268 ret = as_utils_install_icon (location, filename, origin, destdir, error);
1269 break;
1270 }
1271 basename = g_path_get_basename (filename);
1272 tmp = g_strstr_len (basename, -1, "-icons.tar.gz");
1273 if (tmp != NULL) {
1274 *tmp = '\0';
1275 ret = as_utils_install_icon (location, filename, basename, destdir, error);
1276 break;
1277 }
1278
1279 /* unrecognised */
1280 g_set_error_literal (error,
1281 AS_UTILS_ERROR,
1282 AS_UTILS_ERROR_INVALID_TYPE,
1283 "No idea how to process files of this type");
1284 break;
1285 }
1286 return ret;
1287 }
1288
1289 /**
1290 * as_utils_search_token_valid:
1291 * @token: the search token
1292 *
1293 * Checks the search token if it is valid. Valid tokens are at least 3 chars in
1294 * length, not common words like "and", and do not contain markup.
1295 *
1296 * Returns: %TRUE is the search token was valid
1297 *
1298 * Since: 0.3.4
1299 **/
1300 gboolean
as_utils_search_token_valid(const gchar * token)1301 as_utils_search_token_valid (const gchar *token)
1302 {
1303 guint i;
1304 for (i = 0; token[i] != '\0'; i++) {
1305 if (token[i] == '<' ||
1306 token[i] == '>' ||
1307 token[i] == '(' ||
1308 token[i] == ')')
1309 return FALSE;
1310 }
1311 if (i < 3)
1312 return FALSE;
1313 return TRUE;
1314 }
1315
1316 /**
1317 * as_utils_search_tokenize:
1318 * @search: the search string
1319 *
1320 * Splits up a string into tokens and returns tokens that are suitable for
1321 * searching. This includes taking out common words and casefolding the
1322 * returned search tokens.
1323 *
1324 * Returns: (transfer full): Valid tokens to search for, or %NULL for error
1325 *
1326 * Since: 0.3.4
1327 **/
1328 gchar **
as_utils_search_tokenize(const gchar * search)1329 as_utils_search_tokenize (const gchar *search)
1330 {
1331 gchar **values = NULL;
1332 guint i;
1333 guint idx = 0;
1334 g_auto(GStrv) tmp = NULL;
1335
1336 /* only add keywords that are long enough */
1337 tmp = g_strsplit (search, " ", -1);
1338 values = g_new0 (gchar *, g_strv_length (tmp) + 1);
1339 for (i = 0; tmp[i] != NULL; i++) {
1340 if (!as_utils_search_token_valid (tmp[i]))
1341 continue;
1342 values[idx++] = g_utf8_casefold (tmp[i], -1);
1343 }
1344 if (idx == 0) {
1345 g_free (values);
1346 return NULL;
1347 }
1348 return values;
1349 }
1350
1351 static gint
as_utils_vercmp_char(gchar chr1,gchar chr2)1352 as_utils_vercmp_char (gchar chr1, gchar chr2)
1353 {
1354 if (chr1 == chr2)
1355 return 0;
1356 if (chr1 == '~')
1357 return -1;
1358 if (chr2 == '~')
1359 return 1;
1360 return chr1 < chr2 ? -1 : 1;
1361 }
1362
1363 static gint
as_utils_vercmp_chunk(const gchar * str1,const gchar * str2)1364 as_utils_vercmp_chunk (const gchar *str1, const gchar *str2)
1365 {
1366 guint i;
1367
1368 /* trivial */
1369 if (g_strcmp0 (str1, str2) == 0)
1370 return 0;
1371 if (str1 == NULL)
1372 return 1;
1373 if (str2 == NULL)
1374 return -1;
1375
1376 /* check each char of the chunk */
1377 for (i = 0; str1[i] != '\0' && str2[i] != '\0'; i++) {
1378 gint rc = as_utils_vercmp_char (str1[i], str2[i]);
1379 if (rc != 0)
1380 return rc;
1381 }
1382 return as_utils_vercmp_char (str1[i], str2[i]);
1383 }
1384
1385 static gint
as_utils_vercmp_internal(const gchar * version_a,const gchar * version_b)1386 as_utils_vercmp_internal (const gchar *version_a,
1387 const gchar *version_b)
1388 {
1389 guint longest_split;
1390 g_auto(GStrv) split_a = NULL;
1391 g_auto(GStrv) split_b = NULL;
1392
1393 split_a = g_strsplit (version_a, ".", -1);
1394 split_b = g_strsplit (version_b, ".", -1);
1395
1396 longest_split = MAX (g_strv_length (split_a), g_strv_length (split_b));
1397 for (guint i = 0; i < longest_split; i++) {
1398 gchar *endptr_a = NULL;
1399 gchar *endptr_b = NULL;
1400 gint64 ver_a;
1401 gint64 ver_b;
1402
1403 /* we lost or gained a dot */
1404 if (split_a[i] == NULL)
1405 return -1;
1406 if (split_b[i] == NULL)
1407 return 1;
1408
1409 /* compare integers */
1410 ver_a = g_ascii_strtoll (split_a[i], &endptr_a, 10);
1411 ver_b = g_ascii_strtoll (split_b[i], &endptr_b, 10);
1412 if (ver_a < ver_b)
1413 return -1;
1414 if (ver_a > ver_b)
1415 return 1;
1416
1417 /* compare strings */
1418 if ((endptr_a != NULL && endptr_a[0] != '\0') ||
1419 (endptr_b != NULL && endptr_b[0] != '\0')) {
1420 gint rc = as_utils_vercmp_chunk (endptr_a, endptr_b);
1421 if (rc < 0)
1422 return -1;
1423 if (rc > 0)
1424 return 1;
1425 }
1426 }
1427
1428 /* we really shouldn't get here */
1429 return 0;
1430 }
1431
1432 /**
1433 * as_utils_vercmp_full:
1434 * @version_a: the release version, e.g. 1.2.3
1435 * @version_b: the release version, e.g. 1.2.3.1
1436 * @flags: some #AsVersionCompareFlag
1437 *
1438 * Compares version numbers for sorting.
1439 *
1440 * Returns: -1 if a < b, +1 if a > b, 0 if they are equal, and %G_MAXINT on error
1441 *
1442 * Since: 0.7.15
1443 */
1444 gint
as_utils_vercmp_full(const gchar * version_a,const gchar * version_b,AsVersionCompareFlag flags)1445 as_utils_vercmp_full (const gchar *version_a,
1446 const gchar *version_b,
1447 AsVersionCompareFlag flags)
1448 {
1449 /* sanity check */
1450 if (version_a == NULL || version_b == NULL)
1451 return G_MAXINT;
1452
1453 /* optimisation */
1454 if (g_strcmp0 (version_a, version_b) == 0)
1455 return 0;
1456
1457 if (flags & AS_VERSION_COMPARE_FLAG_USE_HEURISTICS) {
1458 /* split into sections, and try to parse */
1459 g_autofree gchar *str_a = as_utils_version_parse (version_a);
1460 g_autofree gchar *str_b = as_utils_version_parse (version_b);
1461 return as_utils_vercmp_internal (str_a, str_b);
1462 } else {
1463 #ifdef HAVE_RPM
1464 return rpmvercmp (version_a, version_b);
1465 #else
1466 return as_utils_vercmp_internal (version_a, version_b);
1467 #endif
1468 }
1469 }
1470
1471 /**
1472 * as_utils_vercmp:
1473 * @version_a: the release version, e.g. 1.2.3
1474 * @version_b: the release version, e.g. 1.2.3.1
1475 *
1476 * Compares version numbers for sorting.
1477 *
1478 * Returns: -1 if a < b, +1 if a > b, 0 if they are equal, and %G_MAXINT on error
1479 *
1480 * Since: 0.3.5
1481 */
1482 gint
as_utils_vercmp(const gchar * version_a,const gchar * version_b)1483 as_utils_vercmp (const gchar *version_a, const gchar *version_b)
1484 {
1485 return as_utils_vercmp_full (version_a, version_b,
1486 AS_VERSION_COMPARE_FLAG_USE_HEURISTICS);
1487 }
1488
1489 /**
1490 * as_ptr_array_find_string:
1491 * @array: gchar* array
1492 * @value: string to find
1493 *
1494 * Finds a string in a pointer array.
1495 *
1496 * Returns: the const string, or %NULL if not found
1497 **/
1498 const gchar *
as_ptr_array_find_string(GPtrArray * array,const gchar * value)1499 as_ptr_array_find_string (GPtrArray *array, const gchar *value)
1500 {
1501 const gchar *tmp;
1502 guint i;
1503 for (i = 0; i < array->len; i++) {
1504 tmp = g_ptr_array_index (array, i);
1505 if (g_strcmp0 (tmp, value) == 0)
1506 return tmp;
1507 }
1508 return NULL;
1509 }
1510
1511 /**
1512 * as_utils_guid_from_data:
1513 * @namespace_id: A namespace ID, e.g. "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
1514 * @data: data to hash
1515 * @data_len: length of @data
1516 * @error: A #GError or %NULL
1517 *
1518 * Returns a GUID for some data. This uses a hash and so even small
1519 * differences in the @data will produce radically different return values.
1520 *
1521 * The implementation is taken from RFC4122, Section 4.1.3; specifically
1522 * using a type-5 SHA-1 hash.
1523 *
1524 * Returns: A new GUID, or %NULL if the namespace_id was invalid
1525 *
1526 * Since: 0.6.13
1527 **/
1528 gchar *
as_utils_guid_from_data(const gchar * namespace_id,const guint8 * data,gsize data_len,GError ** error)1529 as_utils_guid_from_data (const gchar *namespace_id,
1530 const guint8 *data,
1531 gsize data_len,
1532 GError **error)
1533 {
1534 #ifdef _WIN32
1535 g_set_error_literal (error,
1536 AS_UTILS_ERROR,
1537 AS_UTILS_ERROR_FAILED,
1538 "not supported");
1539 return FALSE;
1540 #else
1541 gchar guid_new[37]; /* 36 plus NUL */
1542 gsize digestlen = 20;
1543 guint8 hash[20];
1544 gint rc;
1545 uuid_t uu_namespace;
1546 uuid_t uu_new;
1547 g_autoptr(GChecksum) csum = NULL;
1548
1549 g_return_val_if_fail (namespace_id != NULL, FALSE);
1550 g_return_val_if_fail (data != NULL, FALSE);
1551 g_return_val_if_fail (data_len != 0, FALSE);
1552
1553 /* convert the namespace to binary */
1554 rc = uuid_parse (namespace_id, uu_namespace);
1555 if (rc != 0) {
1556 g_set_error (error,
1557 AS_UTILS_ERROR,
1558 AS_UTILS_ERROR_FAILED,
1559 "namespace '%s' is invalid",
1560 namespace_id);
1561 return FALSE;
1562 }
1563
1564 /* hash the namespace and then the string */
1565 csum = g_checksum_new (G_CHECKSUM_SHA1);
1566 g_checksum_update (csum, (guchar *) uu_namespace, 16);
1567 g_checksum_update (csum, (guchar *) data, (gssize) data_len);
1568 g_checksum_get_digest (csum, hash, &digestlen);
1569
1570 /* copy most parts of the hash 1:1 */
1571 memcpy (uu_new, hash, 16);
1572
1573 /* set specific bits according to Section 4.1.3 */
1574 uu_new[6] = (guint8) ((uu_new[6] & 0x0f) | (5 << 4));
1575 uu_new[8] = (guint8) ((uu_new[8] & 0x3f) | 0x80);
1576
1577 /* return as a string */
1578 uuid_unparse (uu_new, guid_new);
1579 return g_strdup (guid_new);
1580 #endif
1581 }
1582
1583 /**
1584 * as_utils_guid_is_valid:
1585 * @guid: string to check
1586 *
1587 * Checks the source string is a valid string GUID descriptor.
1588 *
1589 * Returns: %TRUE if @guid was a valid GUID, %FALSE otherwise
1590 *
1591 * Since: 0.5.0
1592 **/
1593 gboolean
as_utils_guid_is_valid(const gchar * guid)1594 as_utils_guid_is_valid (const gchar *guid)
1595 {
1596 #ifdef _WIN32
1597 /* XXX Ideally we should set a GError but this was already a public
1598 * API, and it doesn't have such parameter.
1599 */
1600 g_printerr ("%s: not supported\n", G_STRFUNC);
1601 return FALSE;
1602 #else
1603 gint rc;
1604 uuid_t uu;
1605 if (guid == NULL)
1606 return FALSE;
1607 rc = uuid_parse (guid, uu);
1608 return rc == 0;
1609 #endif
1610 }
1611
1612 /**
1613 * as_utils_guid_from_string:
1614 * @str: A source string to use as a key
1615 *
1616 * Returns a GUID for a given string. This uses a hash and so even small
1617 * differences in the @str will produce radically different return values.
1618 *
1619 * The implementation is taken from RFC4122, Section 4.1.3; specifically
1620 * using a type-5 SHA-1 hash with a DNS namespace.
1621 * The same result can be obtained with this simple python program:
1622 *
1623 * #!/usr/bin/python
1624 * import uuid
1625 * print uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
1626 *
1627 * Returns: A new GUID, or %NULL if the string was invalid
1628 *
1629 * Since: 0.5.0
1630 **/
1631 gchar *
as_utils_guid_from_string(const gchar * str)1632 as_utils_guid_from_string (const gchar *str)
1633 {
1634 if (str == NULL)
1635 return NULL;
1636 return as_utils_guid_from_data ("6ba7b810-9dad-11d1-80b4-00c04fd430c8",
1637 (const guint8 *) str, strlen (str), NULL);
1638 }
1639
1640 #define AS_UTILS_DECODE_BCD(val) ((((val) >> 4) & 0x0f) * 10 + ((val) & 0x0f))
1641
1642 /**
1643 * as_utils_version_from_uint32:
1644 * @val: A uint32le version number
1645 * @flags: flags used for formatting, e.g. %AS_VERSION_PARSE_FLAG_USE_TRIPLET
1646 *
1647 * Returns a dotted decimal version string from a 32 bit number.
1648 *
1649 * Returns: A version number, e.g. "1.0.3"
1650 *
1651 * Since: 0.5.2
1652 **/
1653 gchar *
as_utils_version_from_uint32(guint32 val,AsVersionParseFlag flags)1654 as_utils_version_from_uint32 (guint32 val, AsVersionParseFlag flags)
1655 {
1656 if (flags & AS_VERSION_PARSE_FLAG_USE_TRIPLET) {
1657 return g_strdup_printf ("%u.%u.%u",
1658 (val >> 24) & 0xff,
1659 (val >> 16) & 0xff,
1660 val & 0xffff);
1661 }
1662 return g_strdup_printf ("%u.%u.%u.%u",
1663 (val >> 24) & 0xff,
1664 (val >> 16) & 0xff,
1665 (val >> 8) & 0xff,
1666 val & 0xff);
1667 }
1668
1669 /**
1670 * as_utils_version_from_uint16:
1671 * @val: A uint16le version number
1672 * @flags: flags used for formatting, e.g. %AS_VERSION_PARSE_FLAG_USE_TRIPLET
1673 *
1674 * Returns a dotted decimal version string from a 16 bit number.
1675 *
1676 * Returns: A version number, e.g. "1.3"
1677 *
1678 * Since: 0.5.2
1679 **/
1680 gchar *
as_utils_version_from_uint16(guint16 val,AsVersionParseFlag flags)1681 as_utils_version_from_uint16 (guint16 val, AsVersionParseFlag flags)
1682 {
1683 if (flags & AS_VERSION_PARSE_FLAG_USE_BCD) {
1684 return g_strdup_printf ("%u.%u",
1685 AS_UTILS_DECODE_BCD(val >> 8),
1686 AS_UTILS_DECODE_BCD(val));
1687 }
1688 return g_strdup_printf ("%u.%u",
1689 (guint) (val >> 8) & 0xff,
1690 (guint) val & 0xff);
1691 }
1692
1693 /**
1694 * as_utils_version_parse:
1695 * @version: A version number
1696 *
1697 * Returns a dotted decimal version string from a version string. The supported
1698 * formats are:
1699 *
1700 * - Dotted decimal, e.g. "1.2.3"
1701 * - Base 16, a hex number *with* a 0x prefix, e.g. "0x10203"
1702 * - Base 10, a string containing just [0-9], e.g. "66051"
1703 * - Date in YYYYMMDD format, e.g. 20150915
1704 *
1705 * Anything with a '.' or that doesn't match [0-9] or 0x[a-f,0-9] is considered
1706 * a string and returned without modification.
1707 *
1708 * Returns: A version number, e.g. "1.0.3"
1709 *
1710 * Since: 0.5.2
1711 */
1712 gchar *
as_utils_version_parse(const gchar * version)1713 as_utils_version_parse (const gchar *version)
1714 {
1715 const gchar *version_noprefix = version;
1716 gchar *endptr = NULL;
1717 guint64 tmp;
1718 guint base;
1719 guint i;
1720
1721 /* already dotted decimal */
1722 if (g_strstr_len (version, -1, ".") != NULL)
1723 return g_strdup (version);
1724
1725 /* is a date */
1726 if (g_str_has_prefix (version, "20") &&
1727 strlen (version) == 8)
1728 return g_strdup (version);
1729
1730 /* convert 0x prefixed strings to dotted decimal */
1731 if (g_str_has_prefix (version, "0x")) {
1732 version_noprefix += 2;
1733 base = 16;
1734 } else {
1735 /* for non-numeric content, just return the string */
1736 for (i = 0; version[i] != '\0'; i++) {
1737 if (!g_ascii_isdigit (version[i]))
1738 return g_strdup (version);
1739 }
1740 base = 10;
1741 }
1742
1743 /* convert */
1744 tmp = g_ascii_strtoull (version_noprefix, &endptr, base);
1745 if (endptr != NULL && endptr[0] != '\0')
1746 return g_strdup (version);
1747 if (tmp == 0)
1748 return g_strdup (version);
1749 return as_utils_version_from_uint32 ((guint32) tmp, AS_VERSION_PARSE_FLAG_USE_TRIPLET);
1750 }
1751
1752 /**
1753 * as_utils_string_replace:
1754 * @string: The #GString to operate on
1755 * @search: The text to search for
1756 * @replace: The text to use for substitutions
1757 *
1758 * Performs multiple search and replace operations on the given string.
1759 *
1760 * Returns: the number of replacements done, or 0 if @search is not found.
1761 *
1762 * Since: 0.5.11
1763 **/
1764 guint
as_utils_string_replace(GString * string,const gchar * search,const gchar * replace)1765 as_utils_string_replace (GString *string, const gchar *search, const gchar *replace)
1766 {
1767 gchar *tmp;
1768 guint count = 0;
1769 gsize search_idx = 0;
1770 gsize replace_len;
1771 gsize search_len;
1772
1773 g_return_val_if_fail (string != NULL, 0);
1774 g_return_val_if_fail (search != NULL, 0);
1775 g_return_val_if_fail (replace != NULL, 0);
1776
1777 /* nothing to do */
1778 if (string->len == 0)
1779 return 0;
1780
1781 search_len = strlen (search);
1782 replace_len = strlen (replace);
1783
1784 do {
1785 tmp = g_strstr_len (string->str + search_idx, -1, search);
1786 if (tmp == NULL)
1787 break;
1788
1789 /* advance the counter in case @replace contains @search */
1790 search_idx = (gsize) (tmp - string->str);
1791
1792 /* reallocate the string if required */
1793 if (search_len > replace_len) {
1794 g_string_erase (string,
1795 (gssize) search_idx,
1796 (gssize) (search_len - replace_len));
1797 memcpy (tmp, replace, replace_len);
1798 } else if (search_len < replace_len) {
1799 g_string_insert_len (string,
1800 (gssize) search_idx,
1801 replace,
1802 (gssize) (replace_len - search_len));
1803 /* we have to treat this specially as it could have
1804 * been reallocated when the insertion happened */
1805 memcpy (string->str + search_idx, replace, replace_len);
1806 } else {
1807 /* just memcmp in the new string */
1808 memcpy (tmp, replace, replace_len);
1809 }
1810 search_idx += replace_len;
1811 count++;
1812 } while (TRUE);
1813
1814 return count;
1815 }
1816
1817 /**
1818 * as_utils_iso8601_to_datetime: (skip)
1819 * @iso_date: The ISO8601 date
1820 *
1821 * Converts an ISO8601 to a #GDateTime.
1822 *
1823 * Returns: a #GDateTime, or %NULL for error.
1824 *
1825 * Since: 0.6.1
1826 **/
1827 GDateTime *
as_utils_iso8601_to_datetime(const gchar * iso_date)1828 as_utils_iso8601_to_datetime (const gchar *iso_date)
1829 {
1830 GTimeVal tv;
1831 guint dmy[] = {0, 0, 0};
1832
1833 /* nothing set */
1834 if (iso_date == NULL || iso_date[0] == '\0')
1835 return NULL;
1836
1837 /* try to parse complete ISO8601 date */
1838 if (g_strstr_len (iso_date, -1, " ") != NULL) {
1839 if (g_time_val_from_iso8601 (iso_date, &tv) && tv.tv_sec != 0)
1840 return g_date_time_new_from_timeval_utc (&tv);
1841 }
1842
1843 /* g_time_val_from_iso8601() blows goats and won't
1844 * accept a valid ISO8601 formatted date without a
1845 * time value - try and parse this case */
1846 if (sscanf (iso_date, "%u-%u-%u", &dmy[0], &dmy[1], &dmy[2]) != 3)
1847 return NULL;
1848
1849 /* create valid object */
1850 return g_date_time_new_utc ((gint) dmy[0], (gint) dmy[1], (gint) dmy[2], 0, 0, 0);
1851 }
1852
1853 static const gchar *
_as_utils_fix_unique_id_part(const gchar * tmp)1854 _as_utils_fix_unique_id_part (const gchar *tmp)
1855 {
1856 if (tmp == NULL || tmp[0] == '\0')
1857 return AS_APP_UNIQUE_WILDCARD;
1858 return tmp;
1859 }
1860
1861 /**
1862 * as_utils_unique_id_build:
1863 * @scope: a #AsAppScope e.g. %AS_APP_SCOPE_SYSTEM
1864 * @bundle_kind: System, e.g. 'package' or 'flatpak'
1865 * @origin: Origin, e.g. 'fedora' or 'gnome-apps-nightly'
1866 * @kind: #AsAppKind, e.g. %AS_APP_KIND_DESKTOP
1867 * @id: AppStream ID, e.g. 'gimp.desktop'
1868 * @branch: Branch, e.g. '3-20' or 'master'
1869 *
1870 * Builds a valid unique ID using available data.
1871 *
1872 * Returns: a unique name, or %NULL for error;
1873 *
1874 * Since: 0.6.1
1875 */
1876 gchar *
as_utils_unique_id_build(AsAppScope scope,AsBundleKind bundle_kind,const gchar * origin,AsAppKind kind,const gchar * id,const gchar * branch)1877 as_utils_unique_id_build (AsAppScope scope,
1878 AsBundleKind bundle_kind,
1879 const gchar *origin,
1880 AsAppKind kind,
1881 const gchar *id,
1882 const gchar *branch)
1883 {
1884 const gchar *bundle_str = NULL;
1885 const gchar *kind_str = NULL;
1886 const gchar *scope_str = NULL;
1887
1888 g_return_val_if_fail (id != NULL, NULL);
1889
1890 if (kind != AS_APP_KIND_UNKNOWN)
1891 kind_str = as_app_kind_to_string (kind);
1892 if (scope != AS_APP_SCOPE_UNKNOWN)
1893 scope_str = as_app_scope_to_string (scope);
1894 if (bundle_kind != AS_BUNDLE_KIND_UNKNOWN)
1895 bundle_str = as_bundle_kind_to_string (bundle_kind);
1896 return g_strdup_printf ("%s/%s/%s/%s/%s/%s",
1897 _as_utils_fix_unique_id_part (scope_str),
1898 _as_utils_fix_unique_id_part (bundle_str),
1899 _as_utils_fix_unique_id_part (origin),
1900 _as_utils_fix_unique_id_part (kind_str),
1901 _as_utils_fix_unique_id_part (id),
1902 _as_utils_fix_unique_id_part (branch));
1903 }
1904
1905 static inline guint
as_utils_unique_id_find_part(const gchar * str)1906 as_utils_unique_id_find_part (const gchar *str)
1907 {
1908 guint i;
1909 for (i = 0; str[i] != '/' && str[i] != '\0'; i++);
1910 return i;
1911 }
1912
1913 /**
1914 * as_utils_unique_id_valid:
1915 * @unique_id: a unique ID
1916 *
1917 * Checks if a unique ID is valid i.e. has the correct number of
1918 * sections.
1919 *
1920 * Returns: %TRUE if the ID is valid
1921 *
1922 * Since: 0.6.1
1923 */
1924 gboolean
as_utils_unique_id_valid(const gchar * unique_id)1925 as_utils_unique_id_valid (const gchar *unique_id)
1926 {
1927 guint i;
1928 guint sections = 1;
1929 if (unique_id == NULL)
1930 return FALSE;
1931 for (i = 0; unique_id[i] != '\0'; i++) {
1932 if (unique_id[i] == '/')
1933 sections++;
1934 }
1935 return sections == AS_UTILS_UNIQUE_ID_PARTS;
1936 }
1937
1938 static inline gboolean
as_utils_unique_id_is_wildcard_part(const gchar * str,guint len)1939 as_utils_unique_id_is_wildcard_part (const gchar *str, guint len)
1940 {
1941 return len == 1 && str[0] == '*';
1942 }
1943
1944 /**
1945 * as_utils_unique_id_match:
1946 * @unique_id1: a unique ID
1947 * @unique_id2: another unique ID
1948 * @match_flags: a #AsUniqueIdMatchFlags bitfield, e.g. %AS_UNIQUE_ID_MATCH_FLAG_ID
1949 *
1950 * Checks two unique IDs for equality allowing globs to match, whilst also
1951 * allowing clients to whitelist sections that have to match.
1952 *
1953 * Returns: %TRUE if the ID's should be considered equal.
1954 *
1955 * Since: 0.7.8
1956 */
1957 gboolean
as_utils_unique_id_match(const gchar * unique_id1,const gchar * unique_id2,AsUniqueIdMatchFlags match_flags)1958 as_utils_unique_id_match (const gchar *unique_id1,
1959 const gchar *unique_id2,
1960 AsUniqueIdMatchFlags match_flags)
1961 {
1962 guint last1 = 0;
1963 guint last2 = 0;
1964 guint len1;
1965 guint len2;
1966
1967 /* trivial */
1968 if (unique_id1 == unique_id2)
1969 return TRUE;
1970
1971 /* invalid */
1972 if (!as_utils_unique_id_valid (unique_id1) ||
1973 !as_utils_unique_id_valid (unique_id2))
1974 return g_strcmp0 (unique_id1, unique_id2) == 0;
1975
1976 /* look at each part */
1977 for (guint i = 0; i < AS_UTILS_UNIQUE_ID_PARTS; i++) {
1978 const gchar *tmp1 = unique_id1 + last1;
1979 const gchar *tmp2 = unique_id2 + last2;
1980
1981 /* find the slash or the end of the string */
1982 len1 = as_utils_unique_id_find_part (tmp1);
1983 len2 = as_utils_unique_id_find_part (tmp2);
1984
1985 /* either string was a wildcard */
1986 if (match_flags & (1 << i) &&
1987 !as_utils_unique_id_is_wildcard_part (tmp1, len1) &&
1988 !as_utils_unique_id_is_wildcard_part (tmp2, len2)) {
1989 /* are substrings the same */
1990 if (len1 != len2)
1991 return FALSE;
1992 if (memcmp (tmp1, tmp2, len1) != 0)
1993 return FALSE;
1994 }
1995
1996 /* advance to next section */
1997 last1 += len1 + 1;
1998 last2 += len2 + 1;
1999 }
2000 return TRUE;
2001 }
2002
2003 /**
2004 * as_utils_unique_id_equal:
2005 * @unique_id1: a unique ID
2006 * @unique_id2: another unique ID
2007 *
2008 * Checks two unique IDs for equality allowing globs to match.
2009 *
2010 * Returns: %TRUE if the ID's should be considered equal.
2011 *
2012 * Since: 0.6.1
2013 */
2014 gboolean
as_utils_unique_id_equal(const gchar * unique_id1,const gchar * unique_id2)2015 as_utils_unique_id_equal (const gchar *unique_id1, const gchar *unique_id2)
2016 {
2017 return as_utils_unique_id_match (unique_id1,
2018 unique_id2,
2019 AS_UNIQUE_ID_MATCH_FLAG_SCOPE |
2020 AS_UNIQUE_ID_MATCH_FLAG_BUNDLE_KIND |
2021 AS_UNIQUE_ID_MATCH_FLAG_ORIGIN |
2022 AS_UNIQUE_ID_MATCH_FLAG_KIND |
2023 AS_UNIQUE_ID_MATCH_FLAG_ID |
2024 AS_UNIQUE_ID_MATCH_FLAG_BRANCH);
2025 }
2026
2027 /**
2028 * as_utils_unique_id_hash:
2029 * @unique_id: a unique ID
2030 *
2031 * Converts a unique-id to a hash value.
2032 *
2033 * This function implements the widely used DJB hash on the ID subset of the
2034 * unique-id string.
2035 *
2036 * It can be passed to g_hash_table_new() as the hash_func parameter,
2037 * when using non-NULL strings or unique_ids as keys in a GHashTable.
2038 *
2039 * Returns: a hash value corresponding to the key
2040 *
2041 * Since: 0.6.2
2042 */
2043 guint
as_utils_unique_id_hash(const gchar * unique_id)2044 as_utils_unique_id_hash (const gchar *unique_id)
2045 {
2046 gsize i;
2047 guint hash = 5381;
2048 guint section_cnt = 0;
2049
2050 /* not a unique ID */
2051 if (!as_utils_unique_id_valid (unique_id))
2052 return g_str_hash (unique_id);
2053
2054 /* only include the app-id */
2055 for (i = 0; unique_id[i] != '\0'; i++) {
2056 if (unique_id[i] == '/') {
2057 if (++section_cnt > 4)
2058 break;
2059 continue;
2060 }
2061 if (section_cnt < 4)
2062 continue;
2063 hash = (guint) ((hash << 5) + hash) + (guint) (unique_id[i]);
2064 }
2065 return hash;
2066 }
2067
2068 static gboolean
as_utils_appstream_id_is_valid_char(gchar ch)2069 as_utils_appstream_id_is_valid_char (gchar ch)
2070 {
2071 if (g_ascii_isalnum (ch))
2072 return TRUE;
2073 if (ch == '.')
2074 return TRUE;
2075 if (ch == '-')
2076 return TRUE;
2077 return FALSE;
2078 }
2079
2080 /**
2081 * as_utils_appstream_id_build:
2082 * @str: a string to build the AppStream ID from
2083 *
2084 * Fixes a string to be a valid AppStream ID.
2085 *
2086 * This function replaces any invalid chars with an underscore.
2087 *
2088 * Returns: a valid AppStream ID, or %NULL if @str is invalid
2089 *
2090 * Since: 0.6.4
2091 */
2092 gchar *
as_utils_appstream_id_build(const gchar * str)2093 as_utils_appstream_id_build (const gchar *str)
2094 {
2095 gchar *tmp;
2096 guint i;
2097
2098 /* invalid */
2099 if (str == NULL)
2100 return NULL;
2101 if (str[0] == '\0')
2102 return NULL;
2103
2104 tmp = g_strdup (str);
2105 for (i = 0; tmp[i] != '\0'; i++) {
2106 if (!as_utils_appstream_id_is_valid_char (tmp[i]))
2107 tmp[i] = '_';
2108 }
2109 return tmp;
2110 }
2111
2112 /**
2113 * as_utils_appstream_id_valid:
2114 * @str: a string
2115 *
2116 * Checks to see if a string is a valid AppStream ID. A valid AppStream ID only
2117 * contains alphanumeric chars, dots and dashes.
2118 *
2119 * Returns: %TRUE if the string is a valid AppStream ID
2120 *
2121 * Since: 0.6.4
2122 */
2123 gboolean
as_utils_appstream_id_valid(const gchar * str)2124 as_utils_appstream_id_valid (const gchar *str)
2125 {
2126 guint i;
2127 for (i = 0; str[i] != '\0'; i++) {
2128 if (!as_utils_appstream_id_is_valid_char (str[i]))
2129 return FALSE;
2130 }
2131 return TRUE;
2132 }
2133