/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2014-2020 Red Hat, Inc * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Alexander Larsson */ #include "config.h" #include #include #include #include #include "flatpak-ref-utils-private.h" #include "flatpak-run-private.h" #include "flatpak-error.h" #include "flatpak-utils-private.h" FlatpakKinds flatpak_kinds_from_kind (FlatpakRefKind kind) { if (kind == FLATPAK_REF_KIND_RUNTIME) return FLATPAK_KINDS_RUNTIME; return FLATPAK_KINDS_APP; } static gboolean is_valid_initial_name_character (gint c, gboolean allow_dash) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_') || (allow_dash && c == '-'); } static gboolean is_valid_name_character (gint c, gboolean allow_dash) { return is_valid_initial_name_character (c, allow_dash) || (c >= '0' && c <= '9'); } static const char * find_last_char (const char *str, gsize len, int c) { const char *p = str + len - 1; while (p >= str) { if (*p == c) return p; p--; } return NULL; } /** * flatpak_is_valid_name: * @string: The string to check * @len: The string length, or -1 for null-terminated * @error: Return location for an error * * Checks if @string is a valid application name. * * App names are composed of 3 or more elements separated by a period * ('.') character. All elements must contain at least one character. * * Each element must only contain the ASCII characters * "[A-Z][a-z][0-9]_-". Elements may not begin with a digit. * Additionally "-" is only allowed in the last element. * * App names must not begin with a '.' (period) character. * * App names must not exceed 255 characters in length. * * The above means that any app name is also a valid DBus well known * bus name, but not all DBus names are valid app names. The difference are: * 1) DBus name elements may contain '-' in the non-last element. * 2) DBus names require only two elements * * Returns: %TRUE if valid, %FALSE otherwise. */ gboolean flatpak_is_valid_name (const char *string, gssize len, GError **error) { gboolean ret; const gchar *s; const gchar *end; const gchar *last_dot; int dot_count; gboolean last_element; g_return_val_if_fail (string != NULL, FALSE); ret = FALSE; if (len < 0) len = strlen (string); if (G_UNLIKELY (len == 0)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Name can't be empty")); goto out; } if (G_UNLIKELY (len > 255)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Name can't be longer than 255 characters")); goto out; } end = string + len; last_dot = find_last_char (string, len, '.'); last_element = FALSE; s = string; if (G_UNLIKELY (*s == '.')) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Name can't start with a period")); goto out; } else if (G_UNLIKELY (!is_valid_initial_name_character (*s, last_element))) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Name can't start with %c"), *s); goto out; } s += 1; dot_count = 0; while (s != end) { if (*s == '.') { if (s == last_dot) last_element = TRUE; s += 1; if (G_UNLIKELY (s == end)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Name can't end with a period")); goto out; } if (!is_valid_initial_name_character (*s, last_element)) { if (*s == '-') flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Only last name segment can contain -")); else flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Name segment can't start with %c"), *s); goto out; } dot_count++; } else if (G_UNLIKELY (!is_valid_name_character (*s, last_element))) { if (*s == '-') flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Only last name segment can contain -")); else flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Name can't contain %c"), *s); goto out; } s += 1; } if (G_UNLIKELY (dot_count < 2)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Names must contain at least 2 periods")); goto out; } ret = TRUE; out: return ret; } gboolean flatpak_has_name_prefix (const char *string, const char *name) { const char *rest; if (!g_str_has_prefix (string, name)) return FALSE; rest = string + strlen (name); return *rest == 0 || *rest == '.' || !is_valid_name_character (*rest, FALSE); } gboolean flatpak_name_matches_one_wildcard_prefix (const char *name, const char * const *wildcarded_prefixes, gboolean require_exact_match) { const char * const *iter = wildcarded_prefixes; const char *remainder; gsize longest_match_len = 0; /* Find longest valid match */ for (; *iter != NULL; ++iter) { const char *prefix = *iter; gsize prefix_len = strlen (prefix); gsize match_len; gboolean has_wildcard = FALSE; const char *end_of_match; if (g_str_has_suffix (prefix, ".*")) { has_wildcard = TRUE; prefix_len -= 2; } if (strncmp (name, prefix, prefix_len) != 0) continue; end_of_match = name + prefix_len; if (has_wildcard && end_of_match[0] == '.' && is_valid_initial_name_character (end_of_match[1], TRUE)) { end_of_match += 2; while (*end_of_match != 0 && (is_valid_name_character (*end_of_match, TRUE) || (end_of_match[0] == '.' && is_valid_initial_name_character (end_of_match[1], TRUE)))) end_of_match++; } match_len = end_of_match - name; if (match_len > longest_match_len) longest_match_len = match_len; } if (longest_match_len == 0) return FALSE; if (require_exact_match) return name[longest_match_len] == 0; /* non-exact matches can be exact, or can be followed by characters that would make * not be part of the last element in the matched prefix, due to being invalid or * a new element. As a special case we explicitly disallow dash here, even though * it iss typically allowed in the final element of a name, this allows you too sloppily * match org.the.App with org.the.App-symbolic[.png] or org.the.App-settings[.desktop]. */ remainder = name + longest_match_len; return *remainder == 0 || *remainder == '.' || !is_valid_name_character (*remainder, FALSE); } static gboolean is_valid_arch_character (char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_'); } gboolean flatpak_is_valid_arch (const char *string, gssize len, GError **error) { const gchar *end; if (len < 0) len = strlen (string); if (G_UNLIKELY (len == 0)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Arch can't be empty")); return FALSE; } end = string + len; while (string != end) { if (G_UNLIKELY (!is_valid_arch_character (*string))) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Arch can't contain %c"), *string); return FALSE; } string += 1; } return TRUE; } static gboolean is_valid_initial_branch_character (gint c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_') || (c == '-'); } static gboolean is_valid_branch_character (gint c) { return is_valid_initial_branch_character (c) || (c == '.'); } /** * flatpak_is_valid_branch: * @string: The string to check * @len: The string length, or -1 for null-terminated * @error: return location for an error * * Checks if @string is a valid branch name. * * Branch names must only contain the ASCII characters * "[A-Z][a-z][0-9]_-.". * Branch names may not begin with a period. * Branch names must contain at least one character. * * Returns: %TRUE if valid, %FALSE otherwise. */ gboolean flatpak_is_valid_branch (const char *string, gssize len, GError **error) { gboolean ret; const gchar *s; const gchar *end; g_return_val_if_fail (string != NULL, FALSE); ret = FALSE; if (len < 0) len = strlen (string); if (G_UNLIKELY (len == 0)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Branch can't be empty")); goto out; } end = string + len; s = string; if (G_UNLIKELY (!is_valid_initial_branch_character (*s))) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Branch can't start with %c"), *s); goto out; } s += 1; while (s != end) { if (G_UNLIKELY (!is_valid_branch_character (*s))) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, _("Branch can't contain %c"), *s); goto out; } s += 1; } ret = TRUE; out: return ret; } /* Dashes are only valid in the last part of the app id, so we replace them with underscore so we can suffix the id */ char * flatpak_make_valid_id_prefix (const char *orig_id) { char *id, *t; id = g_strdup (orig_id); t = id; while (*t != 0 && *t != '/') { if (*t == '-') *t = '_'; t++; } return id; } static gboolean str_has_suffix (const gchar *str, gsize str_len, const gchar *suffix) { gsize suffix_len; suffix_len = strlen (suffix); if (str_len < suffix_len) return FALSE; return strncmp (str + str_len - suffix_len, suffix, suffix_len) == 0; } gboolean flatpak_id_has_subref_suffix (const char *id, gssize id_len) { if (id_len < 0) id_len = strlen (id); return str_has_suffix (id, id_len, ".Locale") || str_has_suffix (id, id_len, ".Debug") || str_has_suffix (id, id_len, ".Sources"); } static gboolean str_has_prefix (const gchar *str, gsize str_len, const gchar *prefix) { gsize prefix_len; prefix_len = strlen (prefix); if (str_len < prefix_len) return FALSE; return strncmp (str, prefix, prefix_len) == 0; } static const char * skip_segment (const char *s) { const char *slash; slash = strchr (s, '/'); if (slash) return slash + 1; return s + strlen (s); } static int compare_segment (const char *s1, const char *s2) { gint c1, c2; while (*s1 && *s1 != '/' && *s2 && *s2 != '/') { c1 = *s1; c2 = *s2; if (c1 != c2) return c1 - c2; s1++; s2++; } c1 = *s1; if (c1 == '/') c1 = 0; c2 = *s2; if (c2 == '/') c2 = 0; return c1 - c2; } int flatpak_compare_ref (const char *ref1, const char *ref2) { int res; int i; /* Skip first element and do per-segment compares for rest */ for (i = 0; i < 3; i++) { ref1 = skip_segment (ref1); ref2 = skip_segment (ref2); res = compare_segment (ref1, ref2); if (res != 0) return res; } return 0; } struct _FlatpakDecomposed { int ref_count; guint16 ref_offset; guint16 id_offset; guint16 arch_offset; guint16 branch_offset; char *data; /* This is only used when we're directly manipulating sideload repos, by giving * a file:// uri as the remote name. Typically we don't really care about collection ids * internally in flatpak as we use refs tied to a remote. */ char *collection_id; }; static gboolean is_valid_initial_remote_name_character (gint c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_'); } static gboolean is_valid_remote_name_character (gint c) { return is_valid_initial_remote_name_character (c) || c == '-' || c == '.'; } static gboolean is_valid_remote_name (const char *remote, gsize len) { const char *end; if (len == 0) return FALSE; end = remote + len; if (!is_valid_initial_remote_name_character (*remote++)) return FALSE; while (remote < end) { char c = *remote++; if (!is_valid_remote_name_character (c)) return FALSE; } return TRUE; } static FlatpakDecomposed * _flatpak_decomposed_new (char *ref, gboolean allow_refspec, gboolean take, GError **error) { g_autoptr(GError) local_error = NULL; const char *p; const char *slash; gsize ref_offset; gsize id_offset; gsize arch_offset; gsize branch_offset; gsize len; FlatpakDecomposed *decomposed; /* We want to use uint16 to store offset, so fail on uselessly large refs */ len = strlen (ref); if (len > 0xffff) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Ref too long")); return NULL; } p = ref; if (allow_refspec) { const char *colon = strchr (p, ':'); if (colon != NULL) { if (!is_valid_remote_name (ref, colon - ref)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid remote name")); return NULL; } p = colon + 1; } } ref_offset = p - ref; if (g_str_has_prefix (p, "app/")) p += strlen ("app/"); else if (g_str_has_prefix (p, "runtime/")) p += strlen ("runtime/"); else { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("%s is not application or runtime"), ref); return NULL; } id_offset = p - ref; slash = strchr (p, '/'); if (slash == NULL) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Wrong number of components in %s"), ref); return NULL; } if (!flatpak_is_valid_name (p, slash - p, &local_error)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid name %.*s: %s"), (int)(slash - p), p, local_error->message); return NULL; } p = slash + 1; arch_offset = p - ref; slash = strchr (p, '/'); if (slash == NULL) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Wrong number of components in %s"), ref); return NULL; } if (!flatpak_is_valid_arch (p, slash - p, &local_error)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid arch: %.*s: %s"), (int)(slash - p), p, local_error->message); return NULL; } p = slash + 1; branch_offset = p - ref; slash = strchr (p, '/'); if (slash != NULL) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Wrong number of components in %s"), ref); return NULL; } if (!flatpak_is_valid_branch (p, -1, &local_error)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid branch %s: %s"), p, local_error->message); return NULL; } if (take) { decomposed = g_malloc (sizeof (FlatpakDecomposed)); decomposed->data = ref; } else { char *inline_data; /* Store the dup:ed ref inline */ decomposed = g_malloc (sizeof (FlatpakDecomposed) + strlen (ref) + 1); inline_data = (char *)decomposed + sizeof (FlatpakDecomposed); strcpy (inline_data, ref); decomposed->data = inline_data; } decomposed->ref_count = 1; decomposed->collection_id = NULL; decomposed->ref_offset = (guint16)ref_offset; decomposed->id_offset = (guint16)id_offset; decomposed->arch_offset = (guint16)arch_offset; decomposed->branch_offset = (guint16)branch_offset; return decomposed; } FlatpakDecomposed * flatpak_decomposed_new_from_ref (const char *ref, GError **error) { return _flatpak_decomposed_new ((char *)ref, FALSE, FALSE, error); } FlatpakDecomposed * flatpak_decomposed_new_from_refspec (const char *refspec, GError **error) { return _flatpak_decomposed_new ((char *)refspec, TRUE, FALSE, error); } FlatpakDecomposed * flatpak_decomposed_new_from_ref_take (char *ref, GError **error) { return _flatpak_decomposed_new (ref, FALSE, TRUE, error); } FlatpakDecomposed * flatpak_decomposed_new_from_refspec_take (char *refspec, GError **error) { return _flatpak_decomposed_new (refspec, TRUE, TRUE, error); } FlatpakDecomposed * flatpak_decomposed_new_from_col_ref (const char *ref, const char *collection_id, GError **error) { g_autoptr(FlatpakDecomposed) decomposed = NULL; if (collection_id != NULL && !ostree_validate_collection_id (collection_id, error)) return FALSE; decomposed = flatpak_decomposed_new_from_ref (ref, error); if (decomposed == NULL) return FALSE; decomposed->collection_id = g_strdup (collection_id); return g_steal_pointer (&decomposed); } static FlatpakDecomposed * _flatpak_decomposed_new_from_decomposed (FlatpakDecomposed *old, FlatpakKinds opt_kind, const char *opt_id, gssize opt_id_len, const char *opt_arch, gssize opt_arch_len, const char *opt_branch, gssize opt_branch_len, GError **error) { FlatpakDecomposed *decomposed; g_autoptr(GError) local_error = NULL; char *inline_data; const char *kind_str; gsize kind_len; gsize id_len; gsize arch_len; gsize branch_len; gsize ref_len; char *ref; gsize offset; if (old == NULL) { g_assert (opt_kind != 0); g_assert (opt_id != NULL); g_assert (opt_arch != NULL); g_assert (opt_branch != NULL); } if (opt_kind == 0) kind_str = flatpak_decomposed_get_kind_str (old); else if (opt_kind == FLATPAK_KINDS_APP) kind_str = "app"; else kind_str = "runtime"; kind_len = strlen (kind_str); if (opt_id) { if (opt_id_len == -1) id_len = strlen (opt_id); else id_len = opt_id_len; if (!flatpak_is_valid_name (opt_id, id_len, &local_error)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid name %s: %s"), opt_id, local_error->message); return NULL; } } else { opt_id = flatpak_decomposed_peek_id (old, &id_len); } if (opt_arch) { if (opt_arch_len == -1) arch_len = strlen (opt_arch); else arch_len = opt_arch_len; if (!flatpak_is_valid_arch (opt_arch, arch_len, &local_error)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid arch: %s: %s"), opt_arch, local_error->message); return NULL; } } else { opt_arch = flatpak_decomposed_peek_arch (old, &arch_len); } if (opt_branch) { if (opt_branch_len == -1) branch_len = strlen (opt_branch); else branch_len = opt_branch_len; if (!flatpak_is_valid_branch (opt_branch, branch_len, &local_error)) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid branch: %s: %s"), opt_branch, local_error->message); return NULL; } } else { opt_branch = flatpak_decomposed_peek_branch (old, &branch_len); } ref_len = kind_len + 1 + id_len + 1 + arch_len + 1 + branch_len; if (ref_len > 0xffff) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Ref too long")); return NULL; } /* Store the ref inline */ decomposed = g_malloc (sizeof (FlatpakDecomposed) + ref_len + 1); inline_data = (char *)decomposed + sizeof (FlatpakDecomposed); decomposed->ref_count = 1; decomposed->data = inline_data; decomposed->collection_id = NULL; ref = inline_data; offset = 0; decomposed->ref_offset = (guint16)offset; memcpy (ref + offset, kind_str, kind_len); offset += kind_len; memcpy (ref + offset, "/", 1); offset += 1; decomposed->id_offset = (guint16)offset; memcpy (ref + offset, opt_id, id_len); offset += id_len; memcpy (ref + offset, "/", 1); offset += 1; decomposed->arch_offset = (guint16)offset; memcpy (ref + offset, opt_arch, arch_len); offset += arch_len; memcpy (ref + offset, "/", 1); offset += 1; decomposed->branch_offset = (guint16)offset; memcpy (ref + offset, opt_branch, branch_len); offset += branch_len; g_assert (offset == ref_len); *(ref + offset) = 0; return decomposed; } FlatpakDecomposed * flatpak_decomposed_new_from_decomposed (FlatpakDecomposed *old, FlatpakKinds opt_kind, const char *opt_id, const char *opt_arch, const char *opt_branch, GError **error) { return _flatpak_decomposed_new_from_decomposed (old, opt_kind, opt_id, -1, opt_arch, -1, opt_branch, -1, error); } FlatpakDecomposed * flatpak_decomposed_new_from_parts (FlatpakKinds kind, const char *id, const char *arch, const char *branch, GError **error) { g_assert (kind == FLATPAK_KINDS_APP || kind == FLATPAK_KINDS_RUNTIME); g_assert (id != NULL); if (branch == NULL) branch = "master"; if (arch == NULL) arch = flatpak_get_arch (); return flatpak_decomposed_new_from_decomposed (NULL, kind, id, arch, branch, error); } FlatpakDecomposed * flatpak_decomposed_new_from_pref (FlatpakKinds kind, const char *pref, GError **error) { const char *slash; const char *id; const char *arch; const char *branch; g_assert (kind == FLATPAK_KINDS_APP || kind == FLATPAK_KINDS_RUNTIME); g_assert (pref != NULL); id = pref; slash = strchr (id, '/'); if (slash == NULL) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Wrong number of components in partial ref %s"), pref); return NULL; } arch = slash + 1; slash = strchr (arch, '/'); if (slash == NULL) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Wrong number of components in partial ref %s"), pref); return NULL; } branch = slash + 1; slash = strchr (branch, '/'); if (slash != NULL) { flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Wrong number of components in partial ref %s"), pref); return NULL; } return _flatpak_decomposed_new_from_decomposed (NULL, kind, id, arch - id - 1, arch, branch - arch - 1, branch, -1, error); } FlatpakDecomposed * flatpak_decomposed_ref (FlatpakDecomposed *ref) { g_atomic_int_inc (&ref->ref_count); return ref; } void flatpak_decomposed_unref (FlatpakDecomposed *ref) { if (g_atomic_int_dec_and_test (&ref->ref_count)) { char *inline_data = (char *)ref + sizeof (FlatpakDecomposed); if (ref->data != inline_data) g_free (ref->data); g_free (ref->collection_id); g_free (ref); } } const char * flatpak_decomposed_get_ref (FlatpakDecomposed *ref) { return (const char *)&ref->data[ref->ref_offset]; } char * flatpak_decomposed_dup_ref (FlatpakDecomposed *ref) { return g_strdup (flatpak_decomposed_get_ref (ref)); } const char * flatpak_decomposed_get_refspec (FlatpakDecomposed *ref) { return (const char *)&ref->data[0]; } char * flatpak_decomposed_dup_refspec (FlatpakDecomposed *ref) { return g_strdup (flatpak_decomposed_get_refspec (ref)); } char * flatpak_decomposed_dup_remote (FlatpakDecomposed *ref) { if (ref->ref_offset == 0) return NULL; return g_strndup (ref->data, ref->ref_offset - 1); } /* Note: These are always NULL for regular refs, as they generally * tied to a remote and uses the collection_id from that. * The only case this is set is if we enumerate a remote * of the form `file:///path/to/repo`, as we don't then know * which remote name it is from. */ const char * flatpak_decomposed_get_collection_id (FlatpakDecomposed *ref) { return ref->collection_id; } /* Note: These are always NULL for regular refs, as they generally * tied to a remote and uses the collection_id from that. * The only case this is set is if we enumerate a remote * of the form `file:///path/to/repo`, as we don't then know * which remote name it is from. */ char * flatpak_decomposed_dup_collection_id (FlatpakDecomposed *ref) { return g_strdup (ref->collection_id); } gboolean flatpak_decomposed_equal (FlatpakDecomposed *ref_a, FlatpakDecomposed *ref_b) { return strcmp (ref_a->data, ref_b->data) == 0 && g_strcmp0 (ref_a->collection_id, ref_b->collection_id) == 0; } gint flatpak_decomposed_strcmp (FlatpakDecomposed *ref_a, FlatpakDecomposed *ref_b) { int res = strcmp (ref_a->data, ref_b->data); if (res != 0) return res; return g_strcmp0 (ref_a->collection_id, ref_b->collection_id); } gint flatpak_decomposed_strcmp_p (FlatpakDecomposed **ref_a, FlatpakDecomposed **ref_b) { return flatpak_decomposed_strcmp (*ref_a, *ref_b); } gboolean flatpak_decomposed_equal_except_branch (FlatpakDecomposed *ref_a, FlatpakDecomposed *ref_b) { return ref_a->branch_offset == ref_b->branch_offset && strncmp (ref_a->data, ref_b->data, ref_a->branch_offset) == 0 && g_strcmp0 (ref_a->collection_id, ref_b->collection_id) == 0; } gboolean flatpak_decomposed_equal_except_arch (FlatpakDecomposed *ref_a, FlatpakDecomposed *ref_b) { return ref_a->arch_offset == ref_b->arch_offset && strncmp (ref_a->data, ref_b->data, ref_a->arch_offset) == 0 && strcmp (&ref_a->data[ref_a->branch_offset], &ref_b->data[ref_b->branch_offset]) == 0 && g_strcmp0 (ref_a->collection_id, ref_b->collection_id) == 0; } guint flatpak_decomposed_hash (FlatpakDecomposed *ref) { guint h = g_str_hash (ref->data); if (ref->collection_id) h |= g_str_hash (ref->collection_id); return h; } gboolean flatpak_decomposed_is_app (FlatpakDecomposed *ref) { const char *r = flatpak_decomposed_get_ref (ref); return r[0] == 'a'; } gboolean flatpak_decomposed_is_runtime (FlatpakDecomposed *ref) { const char *r = flatpak_decomposed_get_ref (ref); return r[0] == 'r'; } FlatpakKinds flatpak_decomposed_get_kinds (FlatpakDecomposed *ref) { if (flatpak_decomposed_is_app (ref)) return FLATPAK_KINDS_APP; else return FLATPAK_KINDS_RUNTIME; } FlatpakRefKind flatpak_decomposed_get_kind (FlatpakDecomposed *ref) { if (flatpak_decomposed_is_app (ref)) return FLATPAK_REF_KIND_APP; else return FLATPAK_REF_KIND_RUNTIME; } const char * flatpak_decomposed_get_kind_str (FlatpakDecomposed *ref) { if (flatpak_decomposed_is_app (ref)) return "app"; else return "runtime"; } const char * flatpak_decomposed_get_kind_metadata_group (FlatpakDecomposed *ref) { if (flatpak_decomposed_is_app (ref)) return FLATPAK_METADATA_GROUP_APPLICATION; else return FLATPAK_METADATA_GROUP_RUNTIME; } /* A slashed string ends at '/' instead of nul */ static gboolean slashed_str_equal (const char *slashed_str, const char *str) { char c; while ((c = *str) != 0) { char s_c = *slashed_str; if (s_c == '/') return FALSE; /* slashed_str stopped early */ if (s_c != c) return FALSE; /* slashed_str not same */ str++; slashed_str++; } if (*slashed_str != '/') /* str stopped early */ return FALSE; return TRUE; } /* These are for refs, so ascii case only */ static gboolean slashed_str_strcasestr (const char *haystack, gsize haystack_len, const char *needle) { gssize needle_len = strlen (needle); if (needle_len > haystack_len) return FALSE; if (needle_len == 0) return TRUE; for (gssize i = 0; i <= haystack_len - needle_len; i++) { if (g_ascii_strncasecmp (haystack + i, needle, needle_len) == 0) return TRUE; } return FALSE; } const char * flatpak_decomposed_get_pref (FlatpakDecomposed *ref) { return &ref->data[ref->id_offset]; } char * flatpak_decomposed_dup_pref (FlatpakDecomposed *ref) { return g_strdup (flatpak_decomposed_get_pref (ref)); } const char * flatpak_decomposed_peek_id (FlatpakDecomposed *ref, gsize *out_len) { if (out_len) *out_len = ref->arch_offset - ref->id_offset - 1; return &ref->data[ref->id_offset]; } char * flatpak_decomposed_dup_id (FlatpakDecomposed *ref) { gsize len; const char *ref_id = flatpak_decomposed_peek_id (ref, &len); return g_strndup (ref_id, len); } char * flatpak_decomposed_dup_readable_id (FlatpakDecomposed *ref) { gsize len; const char *ref_id = flatpak_decomposed_peek_id (ref, &len); const char *start; gboolean is_debug, is_sources, is_locale, is_docs, is_sdk, is_platform, is_baseapp; GString *s; is_debug = str_has_suffix (ref_id, len, ".Debug"); if (is_debug) len -= strlen (".Debug"); is_sources = str_has_suffix (ref_id, len, ".Sources"); if (is_sources) len -= strlen (".Sources"); is_locale = str_has_suffix (ref_id, len, ".Locale"); if (is_locale) len -= strlen (".Locale"); is_docs = str_has_suffix (ref_id, len, ".Docs"); if (is_docs) len -= strlen (".Docs"); is_baseapp = str_has_suffix (ref_id, len, ".BaseApp"); if (is_baseapp) len -= strlen (".BaseApp"); is_platform = str_has_suffix (ref_id, len, ".Platform"); if (is_platform) len -= strlen (".Platform"); is_sdk = str_has_suffix (ref_id, len, ".Sdk"); if (is_sdk) len -= strlen (".Sdk"); start = ref_id + len; while (start > ref_id && start[-1] != '.') start--; len -= (start - ref_id); s = g_string_new (""); g_string_append_len (s, start, len); if (is_sdk) g_string_append (s, _(" development platform")); if (is_platform) g_string_append (s, _(" platform")); if (is_baseapp) g_string_append (s, _(" application base")); if (is_debug) g_string_append (s, _(" debug symbols")); if (is_sources) g_string_append (s, _(" sourcecode")); if (is_locale) g_string_append (s, _(" translations")); if (is_docs) g_string_append (s, _(" docs")); return g_string_free (s, FALSE); } gboolean flatpak_decomposed_is_id (FlatpakDecomposed *ref, const char *id) { const char *ref_id = flatpak_decomposed_peek_id (ref, NULL); return slashed_str_equal (ref_id, id); } gboolean flatpak_decomposed_id_has_suffix (FlatpakDecomposed *ref, const char *suffix) { gsize id_len; const char *ref_id = flatpak_decomposed_peek_id (ref, &id_len); return str_has_suffix (ref_id, id_len, suffix); } gboolean flatpak_decomposed_id_has_prefix (FlatpakDecomposed *ref, const char *prefix) { gsize id_len; const char *ref_id = flatpak_decomposed_peek_id (ref, &id_len); return str_has_prefix (ref_id, id_len, prefix); } /* See if the given id looks similar to this ref. The * Levenshtein distance constant was chosen pretty arbitrarily. */ gboolean flatpak_decomposed_is_id_fuzzy (FlatpakDecomposed *ref, const char *id) { gsize ref_id_len; const char *ref_id = flatpak_decomposed_peek_id (ref, &ref_id_len); if (slashed_str_strcasestr (ref_id, ref_id_len, id)) return TRUE; return flatpak_levenshtein_distance (id, -1, ref_id, ref_id_len) <= 2; } gboolean flatpak_decomposed_id_is_subref (FlatpakDecomposed *ref) { gsize ref_id_len; const char *ref_id = flatpak_decomposed_peek_id (ref, &ref_id_len); if (!flatpak_decomposed_is_runtime (ref)) return FALSE; return flatpak_id_has_subref_suffix (ref_id, ref_id_len); } gboolean flatpak_decomposed_id_is_subref_of (FlatpakDecomposed *ref, FlatpakDecomposed *parent_ref) { gsize ref_id_len; const char *ref_id = flatpak_decomposed_peek_id (ref, &ref_id_len); gsize parent_id_len; const char *parent_id = flatpak_decomposed_peek_id (parent_ref, &parent_id_len); /* All subrefs are runtimes, even for apps */ if (!flatpak_decomposed_is_runtime (ref)) return FALSE; if (!flatpak_id_has_subref_suffix (ref_id, ref_id_len)) return FALSE; /* Guaranteed to have a dot in it from the above check, so strip last element */ while (ref_id[ref_id_len-1] != '.') ref_id_len--; ref_id_len--; /* And strip dot */ /* Any dashes in the last element of parent_id got converted to underscores to make it a valid prefix, so custom compare here. */ if (ref_id_len != parent_id_len) return FALSE; for (int i = 0; i < ref_id_len; i++) { char c = parent_id[i]; if (c == '-') c = '_'; if (c != ref_id[i]) return FALSE; } /* Check the rest of the ref */ return strcmp (flatpak_decomposed_peek_arch (ref, NULL), flatpak_decomposed_peek_arch (parent_ref, NULL)) == 0; } const char * flatpak_decomposed_peek_arch (FlatpakDecomposed *ref, gsize *out_len) { if (out_len) *out_len = ref->branch_offset - ref->arch_offset - 1; return &ref->data[ref->arch_offset]; } char * flatpak_decomposed_dup_arch (FlatpakDecomposed *ref) { gsize len; const char *ref_arch = flatpak_decomposed_peek_arch (ref, &len); return g_strndup (ref_arch, len); } gboolean flatpak_decomposed_is_arch (FlatpakDecomposed *ref, const char *arch) { const char *ref_arch = flatpak_decomposed_peek_arch (ref, NULL); return slashed_str_equal (ref_arch, arch); } gboolean flatpak_decomposed_is_arches (FlatpakDecomposed *ref, gssize len, const char **arches) { const char *ref_arch = flatpak_decomposed_peek_arch (ref, NULL); if (len < 0) { len = 0; while (arches[len] != NULL) len++; } for (int i = 0; i < len; i++) { if (slashed_str_equal (ref_arch, arches[i])) return TRUE; } return FALSE; } /* We can add a getter for this, because the branch is last so guaranteed to be null-terminated */ const char * flatpak_decomposed_get_branch (FlatpakDecomposed *ref) { return &ref->data[ref->branch_offset]; } const char * flatpak_decomposed_peek_branch (FlatpakDecomposed *ref, gsize *out_len) { const char *branch = flatpak_decomposed_get_branch (ref); if (out_len) *out_len = strlen (branch); return branch; } char * flatpak_decomposed_dup_branch (FlatpakDecomposed *ref) { const char *ref_branch = flatpak_decomposed_peek_branch (ref, NULL); return g_strdup (ref_branch); } gboolean flatpak_decomposed_is_branch (FlatpakDecomposed *ref, const char *branch) { const char *ref_branch = flatpak_decomposed_get_branch (ref); return strcmp (ref_branch, branch) == 0; } static const char * next_element (const char **partial_ref) { const char *slash; const char *end; slash = (const char *) strchr (*partial_ref, '/'); if (slash != NULL) { end = slash; *partial_ref = slash + 1; } else { end = *partial_ref + strlen (*partial_ref); *partial_ref = end; } return end; } FlatpakKinds flatpak_kinds_from_bools (gboolean app, gboolean runtime) { FlatpakKinds kinds = 0; if (app) kinds |= FLATPAK_KINDS_APP; if (runtime) kinds |= FLATPAK_KINDS_RUNTIME; if (kinds == 0) kinds = FLATPAK_KINDS_APP | FLATPAK_KINDS_RUNTIME; return kinds; } static gboolean _flatpak_split_partial_ref_arg (const char *partial_ref, gboolean validate, FlatpakKinds default_kinds, const char *default_arch, const char *default_branch, FlatpakKinds *out_kinds, char **out_id, char **out_arch, char **out_branch, GError **error) { const char *id_start = NULL; const char *id_end = NULL; g_autofree char *id = NULL; const char *arch_start = NULL; const char *arch_end = NULL; g_autofree char *arch = NULL; const char *branch_start = NULL; const char *branch_end = NULL; g_autofree char *branch = NULL; g_autoptr(GError) local_error = NULL; FlatpakKinds kinds = 0; if (g_str_has_prefix (partial_ref, "app/")) { partial_ref += strlen ("app/"); kinds = FLATPAK_KINDS_APP; } else if (g_str_has_prefix (partial_ref, "runtime/")) { partial_ref += strlen ("runtime/"); kinds = FLATPAK_KINDS_RUNTIME; } else kinds = default_kinds; id_start = partial_ref; id_end = next_element (&partial_ref); id = g_strndup (id_start, id_end - id_start); if (validate && !flatpak_is_valid_name (id, -1, &local_error)) return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid id %s: %s"), id, local_error->message); arch_start = partial_ref; arch_end = next_element (&partial_ref); if (arch_end != arch_start) arch = g_strndup (arch_start, arch_end - arch_start); else arch = g_strdup (default_arch); branch_start = partial_ref; branch_end = next_element (&partial_ref); if (branch_end != branch_start) branch = g_strndup (branch_start, branch_end - branch_start); else branch = g_strdup (default_branch); if (validate && branch != NULL && !flatpak_is_valid_branch (branch, -1, &local_error)) return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid branch %s: %s"), branch, local_error->message); if (out_kinds) *out_kinds = kinds; if (out_id != NULL) *out_id = g_steal_pointer (&id); if (out_arch != NULL) *out_arch = g_steal_pointer (&arch); if (out_branch != NULL) *out_branch = g_steal_pointer (&branch); return TRUE; } gboolean flatpak_split_partial_ref_arg (const char *partial_ref, FlatpakKinds default_kinds, const char *default_arch, const char *default_branch, FlatpakKinds *out_kinds, char **out_id, char **out_arch, char **out_branch, GError **error) { return _flatpak_split_partial_ref_arg (partial_ref, TRUE, default_kinds, default_arch, default_branch, out_kinds, out_id, out_arch, out_branch, error); } gboolean flatpak_split_partial_ref_arg_novalidate (const char *partial_ref, FlatpakKinds default_kinds, const char *default_arch, const char *default_branch, FlatpakKinds *out_kinds, char **out_id, char **out_arch, char **out_branch) { return _flatpak_split_partial_ref_arg (partial_ref, FALSE, default_kinds, default_arch, default_branch, out_kinds, out_id, out_arch, out_branch, NULL); } char * flatpak_build_untyped_ref (const char *runtime, const char *branch, const char *arch) { if (arch == NULL) arch = flatpak_get_arch (); return g_build_filename (runtime, arch, branch, NULL); } char * flatpak_build_runtime_ref (const char *runtime, const char *branch, const char *arch) { if (branch == NULL) branch = "master"; if (arch == NULL) arch = flatpak_get_arch (); return g_build_filename ("runtime", runtime, arch, branch, NULL); } char * flatpak_build_app_ref (const char *app, const char *branch, const char *arch) { if (branch == NULL) branch = "master"; if (arch == NULL) arch = flatpak_get_arch (); return g_build_filename ("app", app, arch, branch, NULL); }