Commit message: Disallow @@ and @@u usage in desktop files

Fixes #4146.

(cherry picked from commit 652a28ffab67cb6cd7d12dc3a93979bcd3731c7f)
CVE: CVE-2021-21381
CWE: CWE-94
Vulnerability description: Flatpak is a system for building, distributing, and running sandboxed desktop applications on Linux. In Flatpack since version 0.9.4 and before version 1.10.2 has a vulnerability in the "file forwarding" feature which can be used by an attacker to gain access to files that would not ordinarily be allowed by the app's permissions. By putting the special tokens `@@` and/or `@@u` in the Exec field of a Flatpak app's .desktop file, a malicious app publisher can trick flatpak into behaving as though the user had chosen to open a target file with their Flatpak app, which automatically makes that file available to the Flatpak app. This is fixed in version 1.10.2. A minimal solution is the first commit "`Disallow @@ and @@U usage in desktop files`". The follow-up commits "`dir: Reserve the whole @@ prefix`" and "`dir: Refuse to export .desktop files with suspicious uses of @@ tokens`" are recommended, but not strictly required. As a workaround, avoid installing Flatpak apps from untrusted sources, or check the contents of the exported `.desktop` files in `exports/share/applications/*.desktop` (typically `~/.local/share/flatpak/exports/share/applications/*.desktop` and `/var/lib/flatpak/exports/share/applications/*.desktop`) to make sure that literal filenames do not follow `@@` or `@@u`.

Project: flatpak
File: flatpak-dir.c

rewrite_mime_xml (xmlDoc *doc)
{
  xmlNode *root_element = xmlDocGetRootElement (doc);
  xmlNode *top_node = NULL;

  for (top_node = root_element; top_node; top_node = top_node->next)
    {
      xmlNode *mime_node = NULL;
      if (top_node->type != XML_ELEMENT_NODE)
        continue;

      if (strcmp ((char *) top_node->name, "mime-info") != 0)
        return FALSE;

      for (mime_node = top_node->children; mime_node; mime_node = mime_node->next)
        {
          xmlNode *sub_node = NULL;
          xmlNode *next_sub_node = NULL;

          xml_autofree xmlChar *mimetype = NULL;
          if (mime_node->type != XML_ELEMENT_NODE)
            continue;

          if (strcmp ((char *) mime_node->name, "mime-type") != 0)
            return FALSE;

          mimetype = xmlGetProp (mime_node, (xmlChar *) "type");
          for (sub_node = mime_node->children; sub_node; sub_node = next_sub_node)
            {
              next_sub_node = sub_node->next;

              if (sub_node->type != XML_ELEMENT_NODE)
                continue;

              if (strcmp ((char *) sub_node->name, "magic") == 0)
                {
                  g_warning ("Removing magic mime rule from exports");
                  xmlUnlinkNode (sub_node);
                  xmlFreeNode (sub_node);
                }
              else if (strcmp ((char *) sub_node->name, "glob") == 0)
                {
                  xmlSetProp (sub_node,
                              (const xmlChar *) "weight",
                              (const xmlChar *) "5");
                }
            }
        }
    }

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_install (FlatpakDir          *self,
                     gboolean             no_pull,
                     gboolean             no_deploy,
                     gboolean             no_static_deltas,
                     gboolean             reinstall,
                     gboolean             app_hint,
                     FlatpakRemoteState  *state,
                     FlatpakDecomposed   *ref,
                     const char          *opt_commit,
                     const char         **opt_subpaths,
                     const char         **opt_previous_ids,
                     GFile               *sideload_repo,
                     GBytes              *require_metadata,
                     const char          *token,
                     FlatpakProgress     *progress,
                     GCancellable        *cancellable,
                     GError             **error)
{
  FlatpakPullFlags flatpak_flags;

  flatpak_flags = FLATPAK_PULL_FLAGS_DOWNLOAD_EXTRA_DATA;
  if (no_static_deltas)
    flatpak_flags |= FLATPAK_PULL_FLAGS_NO_STATIC_DELTAS;

  if (flatpak_dir_use_system_helper (self, NULL))
    {
      g_autoptr(OstreeRepo) child_repo = NULL;
      g_auto(GLnxLockFile) child_repo_lock = { 0, };
      const char *installation = flatpak_dir_get_id (self);
      const char *empty_subpaths[] = {NULL};
      const char **subpaths;
      g_autofree char *child_repo_path = NULL;
      FlatpakHelperDeployFlags helper_flags = 0;
      g_autofree char *url = NULL;
      gboolean gpg_verify_summary;
      gboolean gpg_verify;
      gboolean is_oci;
      gboolean is_revokefs_pull = FALSE;

      if (opt_subpaths)
        subpaths = opt_subpaths;
      else
        subpaths = empty_subpaths;

      if (!ostree_repo_remote_get_url (self->repo,
                                       state->remote_name,
                                       &url,
                                       error))
        return FALSE;

      if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, state->remote_name,
                                                      &gpg_verify_summary, error))
        return FALSE;

      if (!ostree_repo_remote_get_gpg_verify (self->repo, state->remote_name,
                                              &gpg_verify, error))
        return FALSE;

      is_oci = flatpak_dir_get_remote_oci (self, state->remote_name);
      if (no_pull)
        {
          /* Do nothing */
        }
      else if (is_oci)
        {
          g_autoptr(FlatpakOciRegistry) registry = NULL;
          g_autoptr(GFile) registry_file = NULL;

          registry = flatpak_dir_create_system_child_oci_registry (self, &child_repo_lock, token, error);
          if (registry == NULL)
            return FALSE;

          registry_file = g_file_new_for_uri (flatpak_oci_registry_get_uri (registry));

          child_repo_path = g_file_get_path (registry_file);

          if (!flatpak_dir_mirror_oci (self, registry, state, flatpak_decomposed_get_ref (ref), opt_commit, NULL, token, progress, cancellable, error))
            return FALSE;
        }
      else if (!gpg_verify_summary || !gpg_verify)
        {
          /* The remote is not gpg verified, so we don't want to allow installation via
             a download in the home directory, as there is no way to verify you're not
             injecting anything into the remote. However, in the case of a remote
             configured to a local filesystem we can just let the system helper do
             the installation, as it can then avoid network i/o and be certain the
             data comes from the right place. */
          if (g_str_has_prefix (url, "file:"))
            helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_LOCAL_PULL;
          else
            return flatpak_fail_error (error, FLATPAK_ERROR_UNTRUSTED, _("Can't pull from untrusted non-gpg verified remote"));
        }
      else
        {
          /* For system pulls, the pull has to be made in a child repo first,
             which is then pulled into the system's one. The pull from child
             repo into the system repo can occur in one of the two following ways:
                1) Hard-link the child repo into system's one.
                2) Copy and verify each object from the child repo to the system's one.

             2) poses the problem of using double disk-space which might fail the
             installation of very big applications. For e.g. at endless, the encyclopedia app
             is about ~6GB, hence ~12GB of free disk-space is required to get it installed.

             For 1), we need to make sure that we address all the security concerns that
             might escalate during the pull from a remote into child repo and subsequently,
             hard-linking it into the (root-owned)system repo. This is taken care of by a
             special FUSE process(revokefs-fuse) which guards all the writes made to the
             child repo and ensures that no file descriptors remain open to the child repo
             before the hard-linkable pull is made into the system's repo.
             More details about the security issues dealt here are present at
             https://github.com/flatpak/flatpak/wiki/Noncopying-system-app-installation

             In case we fail to apply pull approach 1), the pull automatically fallbacks to use 2). */
          g_autofree gchar *src_dir = NULL;
          g_autofree gchar *mnt_dir = NULL;
          g_autoptr(GError) local_error = NULL;

          if (!flatpak_dir_setup_revokefs_fuse_mount (self,
                                                      ref,
                                                      installation,
                                                      &src_dir, &mnt_dir,
                                                      cancellable))
            {
              flatpak_dir_unmount_and_cancel_pull (self, FLATPAK_HELPER_CANCEL_PULL_FLAGS_NONE,
                                                   cancellable,
                                                   &child_repo, &child_repo_lock,
                                                   mnt_dir, src_dir);
            }
          else
            {
              g_autofree gchar *repo_basename = NULL;
              g_autoptr(GFile) mnt_dir_file = NULL;

              mnt_dir_file = g_file_new_for_path (mnt_dir);
              child_repo = flatpak_dir_create_child_repo (self, mnt_dir_file, &child_repo_lock, opt_commit, &local_error);
              if (child_repo == NULL)
                {
                  g_warning ("Cannot create repo on revokefs mountpoint %s: %s", mnt_dir, local_error->message);
                  flatpak_dir_unmount_and_cancel_pull (self,
                                                       FLATPAK_HELPER_CANCEL_PULL_FLAGS_NONE,
                                                       cancellable,
                                                       &child_repo, &child_repo_lock,
                                                       mnt_dir, src_dir);
                  g_clear_error (&local_error);
                }
              else
                {
                  repo_basename = g_file_get_basename (ostree_repo_get_path (child_repo));
                  child_repo_path = g_build_filename (src_dir, repo_basename, NULL);
                  is_revokefs_pull = TRUE;
                }
            }

          /* Fallback if revokefs-fuse setup does not succeed. This makes the pull
           * temporarily use double disk-space. */
          if (!is_revokefs_pull)
            {
             /* We're pulling from a remote source, we do the network mirroring pull as a
                user and hand back the resulting data to the system-helper, that trusts us
                due to the GPG signatures in the repo */
              child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, NULL, error);
              if (child_repo == NULL)
                return FALSE;
              else
                child_repo_path = g_file_get_path (ostree_repo_get_path (child_repo));
            }

          flatpak_flags |= FLATPAK_PULL_FLAGS_SIDELOAD_EXTRA_DATA;

          if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref), opt_commit, subpaths, sideload_repo, require_metadata, token,
                                 child_repo,
                                 flatpak_flags,
                                 0,
                                 progress, cancellable, error))
            {
              if (is_revokefs_pull)
                {
                  flatpak_dir_unmount_and_cancel_pull (self,
                                                       FLATPAK_HELPER_CANCEL_PULL_FLAGS_PRESERVE_PULL,
                                                       cancellable,
                                                       &child_repo, &child_repo_lock,
                                                       mnt_dir, src_dir);
                }

              return FALSE;
            }

          g_assert (child_repo_path != NULL);

          if (is_revokefs_pull &&
              !flatpak_dir_revokefs_fuse_unmount (&child_repo, &child_repo_lock, mnt_dir, &local_error))
            {
              g_propagate_prefixed_error (error, g_steal_pointer (&local_error), 
                      _("Could not unmount revokefs-fuse filesystem at %s: "), mnt_dir);

              if (src_dir &&
                  !flatpak_dir_system_helper_call_cancel_pull (self,
                                                               FLATPAK_HELPER_CANCEL_PULL_FLAGS_PRESERVE_PULL,
                                                               installation ? installation : "",
                                                               src_dir, cancellable, &local_error))
                g_warning ("Error cancelling ongoing pull at %s: %s", src_dir, local_error->message);
              return FALSE;
            }
        }

      if (no_deploy)
        helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_NO_DEPLOY;

      if (reinstall)
        helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_REINSTALL;

      if (app_hint)
        helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_APP_HINT;

      helper_flags |= FLATPAK_HELPER_DEPLOY_FLAGS_INSTALL_HINT;

      if (!flatpak_dir_system_helper_call_deploy (self,
                                                  child_repo_path ? child_repo_path : "",
                                                  helper_flags, flatpak_decomposed_get_ref (ref), state->remote_name,
                                                  (const char * const *) subpaths,
                                                  (const char * const *) opt_previous_ids,
                                                  installation ? installation : "",
                                                  cancellable,
                                                  error))
        return FALSE;

      if (child_repo_path && !is_revokefs_pull)
        (void) glnx_shutil_rm_rf_at (AT_FDCWD, child_repo_path, NULL, NULL);

      return TRUE;
    }

  if (!no_pull)
    {
      if (!flatpak_dir_pull (self, state, flatpak_decomposed_get_ref (ref), opt_commit, opt_subpaths, sideload_repo, require_metadata, token, NULL,
                             flatpak_flags, OSTREE_REPO_PULL_FLAGS_NONE,
                             progress, cancellable, error))
        return FALSE;
    }

  if (!no_deploy)
    {
      if (!flatpak_dir_deploy_install (self, ref, state->remote_name, opt_subpaths,
                                       opt_previous_ids, reinstall, cancellable, error))
        return FALSE;
    }

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

get_locale_langs_from_localed_dbus (GDBusProxy *proxy, GPtrArray *langs)
{
  g_autoptr(GVariant) locale_variant = NULL;
  g_autofree const gchar **strv = NULL;
  gsize i, j;

  locale_variant = g_dbus_proxy_get_cached_property (proxy, "Locale");
  if (locale_variant == NULL)
    return;

  strv = g_variant_get_strv (locale_variant, NULL);

  for (i = 0; strv[i]; i++)
    {
      const gchar *locale = NULL;
      g_autofree char *lang = NULL;

      /* See locale(7) for these categories */
      const char * const categories[] = { "LANG=", "LC_ALL=", "LC_MESSAGES=", "LC_ADDRESS=",
                                          "LC_COLLATE=", "LC_CTYPE=", "LC_IDENTIFICATION=",
                                          "LC_MONETARY=", "LC_MEASUREMENT=", "LC_NAME=",
                                          "LC_NUMERIC=", "LC_PAPER=", "LC_TELEPHONE=",
                                          "LC_TIME=", NULL };

      for (j = 0; categories[j]; j++)
        {
          if (g_str_has_prefix (strv[i], categories[j]))
            {
              locale = strv[i] + strlen (categories[j]);
              break;
            }
        }

      if (locale == NULL || strcmp (locale, "") == 0)
        continue;

      lang = flatpak_get_lang_from_locale (locale);
      if (lang != NULL && !flatpak_g_ptr_array_contains_string (langs, lang))
        g_ptr_array_add (langs, g_steal_pointer (&lang));
    }
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_run_triggers (FlatpakDir   *self,
                          GCancellable *cancellable,
                          GError      **error)
{
  gboolean ret = FALSE;
  g_autoptr(GFileEnumerator) dir_enum = NULL;
  g_autoptr(GFileInfo) child_info = NULL;
  g_autoptr(GFile) triggersdir = NULL;
  GError *temp_error = NULL;
  const char *triggerspath;

  if (flatpak_dir_use_system_helper (self, NULL))
    {
      const char *installation = flatpak_dir_get_id (self);

      if (!flatpak_dir_system_helper_call_run_triggers (self,
                                                        FLATPAK_HELPER_RUN_TRIGGERS_FLAGS_NONE,
                                                        installation ? installation : "",
                                                        cancellable,
                                                        error))
        return FALSE;

      return TRUE;
    }

  triggerspath = g_getenv ("FLATPAK_TRIGGERSDIR");
  if (triggerspath == NULL)
    triggerspath = FLATPAK_TRIGGERDIR;

  g_debug ("running triggers from %s", triggerspath);

  triggersdir = g_file_new_for_path (triggerspath);

  dir_enum = g_file_enumerate_children (triggersdir, "standard::type,standard::name",
                                        0, cancellable, error);
  if (!dir_enum)
    goto out;

  while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL)
    {
      g_autoptr(GFile) child = NULL;
      const char *name;
      GError *trigger_error = NULL;

      name = g_file_info_get_name (child_info);

      child = g_file_get_child (triggersdir, name);

      if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_REGULAR &&
          g_str_has_suffix (name, ".trigger"))
        {
          g_autoptr(GPtrArray) argv_array = NULL;
          /* We need to canonicalize the basedir, because if has a symlink
             somewhere the bind mount will be on the target of that, not
             at that exact path. */
          g_autofree char *basedir_orig = g_file_get_path (self->basedir);
          g_autofree char *basedir = realpath (basedir_orig, NULL);
          g_autoptr(FlatpakBwrap) bwrap = NULL;
          g_autofree char *commandline = NULL;

          g_debug ("running trigger %s", name);

          bwrap = flatpak_bwrap_new (NULL);

          argv_array = g_ptr_array_new_with_free_func (g_free);
#ifndef DISABLE_SANDBOXED_TRIGGERS
          flatpak_bwrap_add_arg (bwrap, flatpak_get_bwrap ());
          flatpak_bwrap_add_args (bwrap,
                                  "--unshare-ipc",
                                  "--unshare-net",
                                  "--unshare-pid",
                                  "--ro-bind", "/", "/",
                                  "--proc", "/proc",
                                  "--dev", "/dev",
                                  "--bind", basedir, basedir,
                                  NULL);
#endif
          flatpak_bwrap_add_args (bwrap,
                                  flatpak_file_get_path_cached (child),
                                  basedir,
                                  NULL);
          flatpak_bwrap_finish (bwrap);

          commandline = flatpak_quote_argv ((const char **) bwrap->argv->pdata, -1);
          g_debug ("Running '%s'", commandline);

          /* We use LEAVE_DESCRIPTORS_OPEN to work around dead-lock, see flatpak_close_fds_workaround */
          if (!g_spawn_sync ("/",
                             (char **) bwrap->argv->pdata,
                             NULL,
                             G_SPAWN_SEARCH_PATH | G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
                             flatpak_bwrap_child_setup_cb, bwrap->fds,
                             NULL, NULL,
                             NULL, &trigger_error))
            {
              g_warning ("Error running trigger %s: %s", name, trigger_error->message);
              g_clear_error (&trigger_error);
            }
        }

      g_clear_object (&child_info);
    }

  if (temp_error != NULL)
    {
      g_propagate_error (error, temp_error);
      goto out;
    }

  ret = TRUE;
out:
  return ret;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_deploy_update (FlatpakDir        *self,
                           FlatpakDecomposed *ref,
                           const char        *checksum_or_latest,
                           const char       **opt_subpaths,
                           const char       **opt_previous_ids,
                           GCancellable      *cancellable,
                           GError           **error)
{
  g_autoptr(GBytes) old_deploy_data = NULL;
  g_auto(GLnxLockFile) lock = { 0, };
  g_autofree const char **old_subpaths = NULL;
  g_autofree char *old_active = NULL;
  const char *old_origin;
  g_autofree char *commit = NULL;
  g_auto(GStrv) previous_ids = NULL;

  if (!flatpak_dir_lock (self, &lock,
                         cancellable, error))
    return FALSE;

  old_deploy_data = flatpak_dir_get_deploy_data (self, ref,
                                                 FLATPAK_DEPLOY_VERSION_ANY,
                                                 cancellable, error);
  if (old_deploy_data == NULL)
    return FALSE;

  old_active = flatpak_dir_read_active (self, ref, cancellable);

  old_origin = flatpak_deploy_data_get_origin (old_deploy_data);
  old_subpaths = flatpak_deploy_data_get_subpaths (old_deploy_data);

  previous_ids = g_strdupv ((char **) flatpak_deploy_data_get_previous_ids (old_deploy_data, NULL));
  if (opt_previous_ids)
    {
      g_auto(GStrv) old_previous_ids = previous_ids;
      previous_ids = flatpak_strv_merge (old_previous_ids, (char **) opt_previous_ids);
    }

  if (!flatpak_dir_deploy (self,
                           old_origin,
                           ref,
                           checksum_or_latest,
                           opt_subpaths ? opt_subpaths : old_subpaths,
                           (const char * const *) previous_ids,
                           cancellable, error))
    return FALSE;

  if (old_active &&
      !flatpak_dir_undeploy (self, ref, old_active,
                             TRUE, FALSE,
                             cancellable, error))
    return FALSE;

  if (flatpak_decomposed_is_app (ref))
    {
      g_autofree char *id = flatpak_decomposed_dup_id (ref);

      if (!flatpak_dir_update_exports (self, id, cancellable, error))
        return FALSE;
    }

  /* Release lock before doing possibly slow prune */
  glnx_release_lock_file (&lock);

  if (!flatpak_dir_mark_changed (self, error))
    return FALSE;

  flatpak_dir_cleanup_removed (self, cancellable, NULL);

  commit = flatpak_dir_read_active (self, ref, cancellable);
  flatpak_dir_log (self, "deploy update", old_origin, flatpak_decomposed_get_ref (ref), commit, old_active, NULL,
                   "Updated %s from %s", flatpak_decomposed_get_ref (ref), old_origin);

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_find_local_refs (FlatpakDir           *self,
                             const char           *remote,
                             const char           *name,
                             const char           *opt_branch,
                             const char           *opt_default_branch,
                             const char           *opt_arch,
                             const char           *opt_default_arch,
                             FlatpakKinds          kinds,
                             FindMatchingRefsFlags flags,
                             GCancellable         *cancellable,
                             GError              **error)
{
  g_autoptr(GHashTable) local_refs = NULL;
  g_autoptr(GHashTable) local_refspecs = NULL;
  g_autoptr(GError) my_error = NULL;
  g_autofree char *refspec_prefix = g_strconcat (remote, ":.", NULL);
  g_autoptr(GPtrArray) matched_refs = NULL;

  if (!flatpak_dir_ensure_repo (self, NULL, error))
    return NULL;

  if (!ostree_repo_list_refs (self->repo,
                              refspec_prefix,
                              &local_refspecs, cancellable, error))
    return NULL;

  local_refs = refspecs_decompose_steal (local_refspecs);

  matched_refs = find_matching_refs (local_refs,
                                     name,
                                     opt_branch,
                                     opt_default_branch,
                                     opt_arch,
                                     opt_default_arch,
                                     kinds,
                                     flags,
                                     &my_error);
  if (matched_refs == NULL)
    {
      if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
        {
          g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                       _("Error searching local repository: %s"),
                       my_error->message);
          return NULL;
        }
      else
        {
          g_propagate_error (error, g_steal_pointer (&my_error));
          return NULL;
        }
    }

  return g_steal_pointer (&matched_refs);
}

Project: flatpak
File: flatpak-dir.c

remove_unless_decomposed_in_hash (gpointer key,
                                  gpointer value,
                                  gpointer user_data)
{
  GHashTable *table = user_data;
  const FlatpakDecomposed *d = key;

  return !g_hash_table_contains (table, d);
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_deploy (FlatpakDir          *self,
                    const char          *origin,
                    FlatpakDecomposed   *ref,
                    const char          *checksum_or_latest,
                    const char * const * subpaths,
                    const char * const * previous_ids,
                    GCancellable        *cancellable,
                    GError             **error)
{
  g_autofree char *resolved_ref = NULL;
  g_autofree char *ref_id = NULL;
  g_autoptr(GFile) root = NULL;
  g_autoptr(GFile) deploy_base = NULL;
  g_autoptr(GFile) checkoutdir = NULL;
  g_autoptr(GFile) bindir = NULL;
  g_autofree char *checkoutdirpath = NULL;
  g_autoptr(GFile) real_checkoutdir = NULL;
  g_autoptr(GFile) dotref = NULL;
  g_autoptr(GFile) files_etc = NULL;
  g_autoptr(GFile) deploy_data_file = NULL;
  g_autoptr(GVariant) commit_data = NULL;
  g_autoptr(GBytes) deploy_data = NULL;
  g_autoptr(GFile) export = NULL;
  g_autoptr(GFile) extradir = NULL;
  g_autoptr(GKeyFile) keyfile = NULL;
  guint64 installed_size = 0;
  OstreeRepoCheckoutAtOptions options = { 0, };
  const char *checksum;
  glnx_autofd int checkoutdir_dfd = -1;
  g_autoptr(GFile) tmp_dir_template = NULL;
  g_autofree char *tmp_dir_path = NULL;
  const char *xa_ref = NULL;
  g_autofree char *checkout_basename = NULL;
  gboolean created_extra_data = FALSE;
  g_autoptr(GVariant) commit_metadata = NULL;
  g_auto(GLnxLockFile) lock = { 0, };
  g_autoptr(GFile) metadata_file = NULL;
  g_autofree char *metadata_contents = NULL;
  gboolean is_oci;

  if (!flatpak_dir_ensure_repo (self, cancellable, error))
    return FALSE;

  ref_id = flatpak_decomposed_dup_id (ref);

  /* Keep a shared repo lock to avoid prunes removing objects we're relying on
   * while we do the checkout. This could happen if the ref changes after we
   * read its current value for the checkout. */
  if (!flatpak_dir_repo_lock (self, &lock, LOCK_SH, cancellable, error))
    return FALSE;

  deploy_base = flatpak_dir_get_deploy_dir (self, ref);

  if (checksum_or_latest == NULL)
    {
      g_debug ("No checksum specified, getting tip of %s from origin %s", flatpak_decomposed_get_ref (ref), origin);

      resolved_ref = flatpak_dir_read_latest (self, origin, flatpak_decomposed_get_ref (ref), NULL, cancellable, error);
      if (resolved_ref == NULL)
        {
          g_prefix_error (error, _("While trying to resolve ref %s: "), flatpak_decomposed_get_ref (ref));
          return FALSE;
        }

      checksum = resolved_ref;
      g_debug ("tip resolved to: %s", checksum);
    }
  else
    {
      checksum = checksum_or_latest;
      g_debug ("Looking for checksum %s in local repo", checksum);
      if (!ostree_repo_read_commit (self->repo, checksum, NULL, NULL, cancellable, NULL))
        return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("%s is not available"), flatpak_decomposed_get_ref (ref));
    }

  if (!ostree_repo_load_commit (self->repo, checksum, &commit_data, NULL, error))
    return FALSE;

  commit_metadata = g_variant_get_child_value (commit_data, 0);
  checkout_basename = flatpak_dir_get_deploy_subdir (self, checksum, subpaths);

  real_checkoutdir = g_file_get_child (deploy_base, checkout_basename);
  if (g_file_query_exists (real_checkoutdir, cancellable))
    return flatpak_fail_error (error, FLATPAK_ERROR_ALREADY_INSTALLED,
                               _("%s commit %s already installed"), flatpak_decomposed_get_ref (ref), checksum);

  g_autofree char *template = g_strdup_printf (".%s-XXXXXX", checkout_basename);
  tmp_dir_template = g_file_get_child (deploy_base, template);
  tmp_dir_path = g_file_get_path (tmp_dir_template);

  if (g_mkdtemp_full (tmp_dir_path, 0755) == NULL)
    {
      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                           _("Can't create deploy directory"));
      return FALSE;
    }

  checkoutdir = g_file_new_for_path (tmp_dir_path);

  if (!ostree_repo_read_commit (self->repo, checksum, &root, NULL, cancellable, error))
    {
      g_prefix_error (error, _("Failed to read commit %s: "), checksum);
      return FALSE;
    }

  if (!flatpak_repo_collect_sizes (self->repo, root, &installed_size, NULL, cancellable, error))
    return FALSE;

  options.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
  options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
  options.enable_fsync = FALSE; /* We checkout to a temp dir and sync before moving it in place */
  options.bareuseronly_dirs = TRUE; /* https://github.com/ostreedev/ostree/pull/927 */
  checkoutdirpath = g_file_get_path (checkoutdir);

  if (subpaths == NULL || *subpaths == NULL)
    {
      if (!ostree_repo_checkout_at (self->repo, &options,
                                    AT_FDCWD, checkoutdirpath,
                                    checksum,
                                    cancellable, error))
        {
          g_prefix_error (error, _("While trying to checkout %s into %s: "), checksum, checkoutdirpath);
          return FALSE;
        }
    }
  else
    {
      g_autoptr(GFile) files = g_file_get_child (checkoutdir, "files");
      int i;

      if (!g_file_make_directory_with_parents (files, cancellable, error))
        return FALSE;

      options.subpath = "/metadata";

      if (!ostree_repo_checkout_at (self->repo, &options,
                                    AT_FDCWD, checkoutdirpath,
                                    checksum,
                                    cancellable, error))
        {
          g_prefix_error (error, _("While trying to checkout metadata subpath: "));
          return FALSE;
        }

      for (i = 0; subpaths[i] != NULL; i++)
        {
          g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL);
          g_autofree char *dstpath = g_build_filename (checkoutdirpath, "/files", subpaths[i], NULL);
          g_autofree char *dstpath_parent = g_path_get_dirname (dstpath);
          g_autoptr(GFile) child = NULL;

          child = g_file_resolve_relative_path (root, subpath);

          if (!g_file_query_exists (child, cancellable))
            {
              g_debug ("subpath %s not in tree", subpaths[i]);
              continue;
            }

          if (g_mkdir_with_parents (dstpath_parent, 0755))
            {
              glnx_set_error_from_errno (error);
              return FALSE;
            }

          options.subpath = subpath;
          if (!ostree_repo_checkout_at (self->repo, &options,
                                        AT_FDCWD, dstpath,
                                        checksum,
                                        cancellable, error))
            {
              g_prefix_error (error, _("While trying to checkout subpath ‘%s’: "), subpath);
              return FALSE;
            }
        }
    }

  /* Extract any extra data */
  extradir = g_file_resolve_relative_path (checkoutdir, "files/extra");
  if (!flatpak_rm_rf (extradir, cancellable, error))
    {
      g_prefix_error (error, _("While trying to remove existing extra dir: "));
      return FALSE;
    }

  if (!extract_extra_data (self, checksum, extradir, &created_extra_data, cancellable, error))
    return FALSE;

  if (created_extra_data)
    {
      if (!apply_extra_data (self, checkoutdir, cancellable, error))
        {
          g_prefix_error (error, _("While trying to apply extra data: "));
          return FALSE;
        }
    }

  g_variant_lookup (commit_metadata, "xa.ref", "&s", &xa_ref);
  if (xa_ref != NULL)
    {
      gboolean gpg_verify_summary;

      if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, origin, &gpg_verify_summary, error))
        return FALSE;

      if (gpg_verify_summary)
        {
          /* If we're using signed summaries, then the security is really due to the signatures on
           * the summary, and the xa.ref is not needed for security. In particular, endless are
           * currently using one single commit on multiple branches to handle devel/stable promotion.
           * So, to support this we report branch discrepancies as a warning, rather than as an error.
           * See https://github.com/flatpak/flatpak/pull/1013 for more discussion.
           */
          FlatpakDecomposed *checkout_ref = ref;
          g_autoptr(FlatpakDecomposed) commit_ref = NULL;

          commit_ref = flatpak_decomposed_new_from_ref (xa_ref, error);
          if (commit_ref == NULL)
            {
              g_prefix_error (error, _("Invalid commit ref %s: "), xa_ref);
              return FALSE;
            }

          /* Fatal if kind/name/arch don't match. Warn for branch mismatch. */
          if (!flatpak_decomposed_equal_except_branch (checkout_ref, commit_ref))
            {
              g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                           _("Deployed ref %s does not match commit (%s)"),
                           flatpak_decomposed_get_ref (ref), xa_ref);
              return FALSE;
            }

          if (strcmp (flatpak_decomposed_get_branch (checkout_ref), flatpak_decomposed_get_branch (commit_ref)) != 0)
            g_warning (_("Deployed ref %s branch does not match commit (%s)"),
                       flatpak_decomposed_get_ref (ref), xa_ref);
        }
      else if (strcmp (flatpak_decomposed_get_ref (ref), xa_ref) != 0)
        {
          g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                       _("Deployed ref %s does not match commit (%s)"), flatpak_decomposed_get_ref (ref), xa_ref);
          return FALSE;
        }
    }

  keyfile = g_key_file_new ();
  metadata_file = g_file_resolve_relative_path (checkoutdir, "metadata");
  if (g_file_load_contents (metadata_file, NULL,
                            &metadata_contents, NULL, NULL, NULL))
    {
      if (!g_key_file_load_from_data (keyfile,
                                      metadata_contents,
                                      -1,
                                      0, error))
        return FALSE;

      if (!flatpak_check_required_version (flatpak_decomposed_get_ref (ref), keyfile, error))
        return FALSE;
    }

  /* Check the metadata in the commit to make sure it matches the actual
   * deployed metadata, in case we relied on the one in the commit for
   * a decision
   * Note: For historical reason we don't enforce commits to contain xa.metadata
   * since this was lacking in fedora builds.
   */
  is_oci = flatpak_dir_get_remote_oci (self, origin);
  if (!validate_commit_metadata (commit_data, flatpak_decomposed_get_ref (ref),
                                 metadata_contents, !is_oci, error))
    return FALSE;

  dotref = g_file_resolve_relative_path (checkoutdir, "files/.ref");
  if (!g_file_replace_contents (dotref, "", 0, NULL, FALSE,
                                G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error))
    return TRUE;

  export = g_file_get_child (checkoutdir, "export");

  /* Never export any binaries bundled with the app */
  bindir = g_file_get_child (export, "bin");
  if (!flatpak_rm_rf (bindir, cancellable, error))
    return FALSE;

  if (flatpak_decomposed_is_runtime (ref))
    {
      /* Ensure that various files exists as regular files in /usr/etc, as we
         want to bind-mount over them */
      files_etc = g_file_resolve_relative_path (checkoutdir, "files/etc");
      if (g_file_query_exists (files_etc, cancellable))
        {
          char *etcfiles[] = {"passwd", "group", "machine-id" };
          g_autoptr(GFile) etc_resolve_conf = g_file_get_child (files_etc, "resolv.conf");
          int i;
          for (i = 0; i < G_N_ELEMENTS (etcfiles); i++)
            {
              g_autoptr(GFile) etc_file = g_file_get_child (files_etc, etcfiles[i]);
              GFileType type;

              type = g_file_query_file_type (etc_file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                             cancellable);
              if (type == G_FILE_TYPE_REGULAR)
                continue;

              if (type != G_FILE_TYPE_UNKNOWN)
                {
                  /* Already exists, but not regular, probably symlink. Remove it */
                  if (!g_file_delete (etc_file, cancellable, error))
                    return FALSE;
                }

              if (!g_file_replace_contents (etc_file, "", 0, NULL, FALSE,
                                            G_FILE_CREATE_REPLACE_DESTINATION,
                                            NULL, cancellable, error))
                return FALSE;
            }

          if (g_file_query_exists (etc_resolve_conf, cancellable) &&
              !g_file_delete (etc_resolve_conf, cancellable, error))
            return TRUE;

          if (!g_file_make_symbolic_link (etc_resolve_conf,
                                          "/run/host/monitor/resolv.conf",
                                          cancellable, error))
            return FALSE;
        }

      /* Runtime should never export anything */
      if (!flatpak_rm_rf (export, cancellable, error))
        return FALSE;
    }
  else /* is app */
    {
      g_autofree char *ref_arch = flatpak_decomposed_dup_arch (ref);
      g_autofree char *ref_branch = flatpak_decomposed_dup_branch (ref);
      g_autoptr(GFile) wrapper = g_file_get_child (bindir, ref_id);
      g_autofree char *escaped_app = maybe_quote (ref_id);
      g_autofree char *escaped_branch = maybe_quote (ref_branch);
      g_autofree char *escaped_arch = maybe_quote (ref_arch);
      g_autofree char *bin_data = NULL;
      int r;

      if (!flatpak_mkdir_p (bindir, cancellable, error))
        return FALSE;

      if (!flatpak_rewrite_export_dir (ref_id, ref_branch, ref_arch,
                                       keyfile, previous_ids, export,
                                       cancellable,
                                       error))
        return FALSE;

      bin_data = g_strdup_printf ("#!/bin/sh\nexec %s/flatpak run --branch=%s --arch=%s %s \"$@\"\n",
                                  FLATPAK_BINDIR, escaped_branch, escaped_arch, escaped_app);
      if (!g_file_replace_contents (wrapper, bin_data, strlen (bin_data), NULL, FALSE,
                                    G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error))
        return FALSE;

      do
        r = fchmodat (AT_FDCWD, flatpak_file_get_path_cached (wrapper), 0755, 0);
      while (G_UNLIKELY (r == -1 && errno == EINTR));
      if (r == -1)
        return glnx_throw_errno_prefix (error, "fchmodat");
    }

  deploy_data = flatpak_dir_new_deploy_data (self,
                                             checkoutdir,
                                             commit_data,
                                             commit_metadata,
                                             keyfile,
                                             ref_id,
                                             origin,
                                             checksum,
                                             (char **) subpaths,
                                             installed_size,
                                             previous_ids);

  /* Check the app is actually allowed to be used by this user. This can block
   * on getting authorisation. */
  if (!flatpak_dir_check_parental_controls (self, flatpak_decomposed_get_ref (ref), deploy_data, cancellable, error))
    return FALSE;

  deploy_data_file = g_file_get_child (checkoutdir, "deploy");
  if (!flatpak_bytes_save (deploy_data_file, deploy_data, cancellable, error))
    return FALSE;

  if (!glnx_opendirat (AT_FDCWD, checkoutdirpath, TRUE, &checkoutdir_dfd, error))
    return FALSE;

  if (syncfs (checkoutdir_dfd) != 0)
    {
      glnx_set_error_from_errno (error);
      return FALSE;
    }

  if (!g_file_move (checkoutdir, real_checkoutdir, G_FILE_COPY_NO_FALLBACK_FOR_MOVE,
                    cancellable, NULL, NULL, error))
    return FALSE;

  if (!flatpak_dir_set_active (self, ref, checkout_basename, cancellable, error))
    return FALSE;

  if (!flatpak_dir_update_deploy_ref (self, flatpak_decomposed_get_ref (ref), checksum, error))
    return FALSE;

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_get_config_strv (FlatpakDir *self, char *key) 
{
  GKeyFile *config = flatpak_dir_get_repo_config (self);
  g_auto(GStrv) lang = NULL;

  if (config)
    {
      if (g_key_file_has_key (config, "core", key, NULL))
        {
          lang = g_key_file_get_string_list (config, "core", key, NULL, NULL);
          return g_steal_pointer (&lang);
        }
    }
  return NULL;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_find_local_related_for_metadata (FlatpakDir        *self,
                                             FlatpakDecomposed *ref,
                                             const char        *remote_name, /* nullable */
                                             GKeyFile          *metakey,
                                             GCancellable      *cancellable,
                                             GError           **error)
{
  int i;
  g_autoptr(GPtrArray) related = g_ptr_array_new_with_free_func ((GDestroyNotify) flatpak_related_free);
  g_auto(GStrv) groups = NULL;
  g_autofree char *ref_arch = flatpak_decomposed_dup_arch (ref);
  g_autofree char *ref_branch = flatpak_decomposed_dup_branch (ref);

  if (!flatpak_dir_ensure_repo (self, cancellable, error))
    return NULL;

  groups = g_key_file_get_groups (metakey, NULL);
  for (i = 0; groups[i] != NULL; i++)
    {
      char *tagged_extension;

      if (g_str_has_prefix (groups[i], FLATPAK_METADATA_GROUP_PREFIX_EXTENSION) &&
          *(tagged_extension = (groups[i] + strlen (FLATPAK_METADATA_GROUP_PREFIX_EXTENSION))) != 0)
        {
          g_autofree char *extension = NULL;
          g_autofree char *version = g_key_file_get_string (metakey, groups[i],
                                                            FLATPAK_METADATA_KEY_VERSION, NULL);
          g_auto(GStrv) versions = g_key_file_get_string_list (metakey, groups[i],
                                                               FLATPAK_METADATA_KEY_VERSIONS,
                                                               NULL, NULL);
          gboolean subdirectories = g_key_file_get_boolean (metakey, groups[i],
                                                            FLATPAK_METADATA_KEY_SUBDIRECTORIES, NULL);
          gboolean no_autodownload = g_key_file_get_boolean (metakey, groups[i],
                                                             FLATPAK_METADATA_KEY_NO_AUTODOWNLOAD, NULL);
          g_autofree char *download_if = g_key_file_get_string (metakey, groups[i],
                                                                FLATPAK_METADATA_KEY_DOWNLOAD_IF, NULL);
          g_autofree char *autoprune_unless = g_key_file_get_string (metakey, groups[i],
                                                                     FLATPAK_METADATA_KEY_AUTOPRUNE_UNLESS, NULL);
          gboolean autodelete = g_key_file_get_boolean (metakey, groups[i],
                                                        FLATPAK_METADATA_KEY_AUTODELETE, NULL);
          gboolean locale_subset = g_key_file_get_boolean (metakey, groups[i],
                                                           FLATPAK_METADATA_KEY_LOCALE_SUBSET, NULL);
          const char *default_branches[] = { NULL, NULL};
          const char **branches;
          int branch_i;

          /* Parse actual extension name */
          flatpak_parse_extension_with_tag (tagged_extension, &extension, NULL);

          if (versions)
            branches = (const char **) versions;
          else
            {
              if (version)
                default_branches[0] = version;
              else
                default_branches[0] = ref_branch;
              branches = default_branches;
            }

          for (branch_i = 0; branches[branch_i] != NULL; branch_i++)
            {
              g_autoptr(FlatpakDecomposed) extension_ref = NULL;
              g_autofree char *checksum = NULL;
              g_autoptr(GBytes) deploy_data = NULL;
              const char *branch = branches[branch_i];

              extension_ref = flatpak_decomposed_new_from_parts (FLATPAK_KINDS_RUNTIME,
                                                                 extension, ref_arch, branch, NULL);
              if (remote_name != NULL &&
                  flatpak_repo_resolve_rev (self->repo,
                                            NULL,
                                            remote_name,
                                            flatpak_decomposed_get_ref (extension_ref),
                                            FALSE,
                                            &checksum,
                                            NULL,
                                            NULL))
                {
                  add_related (self, related, extension, extension_ref,
                               checksum, no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
                }
              else if ((deploy_data = flatpak_dir_get_deploy_data (self, extension_ref,
                                                                   FLATPAK_DEPLOY_VERSION_ANY,
                                                                   NULL, NULL)) != NULL &&
                       (remote_name == NULL || g_strcmp0 (flatpak_deploy_data_get_origin (deploy_data), remote_name) == 0))
                {
                  /* Here we're including extensions that are deployed but might
                   * not have a ref in the repo, as happens with remote-delete
                   * --force
                   */
                  checksum = g_strdup (flatpak_deploy_data_get_commit (deploy_data));
                  add_related (self, related, extension, extension_ref,
                               checksum, no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
                }
              else if (subdirectories)
                {
                  g_autoptr(GHashTable) matches = local_match_prefix (self, extension_ref, remote_name);
                  GLNX_HASH_TABLE_FOREACH (matches, FlatpakDecomposed *, match)
                    {
                      g_autofree char *match_checksum = NULL;
                      g_autoptr(GBytes) match_deploy_data = NULL;

                      if (remote_name != NULL &&
                          flatpak_repo_resolve_rev (self->repo,
                                                    NULL,
                                                    remote_name,
                                                    flatpak_decomposed_get_ref (match),
                                                    FALSE,
                                                    &match_checksum,
                                                    NULL,
                                                    NULL))
                        {
                          add_related (self, related, extension, match, match_checksum,
                                       no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
                        }
                      else if ((match_deploy_data = flatpak_dir_get_deploy_data (self, match,
                                                                                 FLATPAK_DEPLOY_VERSION_ANY,
                                                                                 NULL, NULL)) != NULL &&
                               (remote_name == NULL || g_strcmp0 (flatpak_deploy_data_get_origin (match_deploy_data), remote_name) == 0))
                        {
                          /* Here again we're including extensions that are deployed but might
                           * not have a ref in the repo
                           */
                          match_checksum = g_strdup (flatpak_deploy_data_get_commit (match_deploy_data));
                          add_related (self, related, extension, match, match_checksum,
                                       no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
                        }
                    }
                }
            }
        }
    }

  return g_steal_pointer (&related);
}

Project: flatpak
File: flatpak-dir.c

local_match_prefix (FlatpakDir        *self,
                    FlatpakDecomposed *extension_ref,
                    const char        *remote)
{
  GHashTable *matches = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL);
  FlatpakKinds kind;
  g_autofree char *id = NULL;
  g_autofree char *arch = NULL;
  g_autofree char *branch = NULL;
  g_autofree char *id_prefix = NULL;
  g_autoptr(GHashTable) refs = NULL;
  g_autofree char *list_prefix = NULL;
  const char *kind_str;

  kind = flatpak_decomposed_get_kinds (extension_ref);
  id = flatpak_decomposed_dup_id (extension_ref);
  arch = flatpak_decomposed_dup_arch (extension_ref);
  branch = flatpak_decomposed_dup_branch (extension_ref);

  id_prefix = g_strconcat (id, ".", NULL);

  kind_str = flatpak_decomposed_get_kind_str (extension_ref);

  if (remote)
    list_prefix = g_strdup_printf ("%s:%s", remote, kind_str);

  if (ostree_repo_list_refs (self->repo, list_prefix, &refs, NULL, NULL))
    {
      GHashTableIter hash_iter;
      gpointer key;

      g_hash_table_iter_init (&hash_iter, refs);
      while (g_hash_table_iter_next (&hash_iter, &key, NULL))
        {
          const char *partial_ref_and_origin = key;
          g_autofree char *partial_ref_store = NULL;
          const char *partial_ref;
          g_autoptr(FlatpakDecomposed) matched = NULL;

          ostree_parse_refspec (partial_ref_and_origin, NULL, &partial_ref_store, NULL);
          if (remote == NULL)
            {
              /* If we're not filtering via list_prefix we need to filter by part[0] manually */
              char *slash = strchr (partial_ref_store, '/');
              if (slash == NULL)
                continue;
              *slash = 0;
              if (strcmp (partial_ref_store, kind_str) != 0)
                continue;
              partial_ref = slash + 1;
            }
          else
            partial_ref = partial_ref_store;

          matched = flatpak_decomposed_new_from_pref (kind, partial_ref, NULL);
          if (matched == NULL)
            continue;

          /* Must match type, arch, branch */
          if (!flatpak_decomposed_is_arch (matched, arch) ||
              !flatpak_decomposed_is_branch (matched, branch))
            continue;

          /* But only prefix of id */
          if (!flatpak_decomposed_id_has_prefix (matched, id_prefix))
            continue;

          g_hash_table_add (matches, g_steal_pointer (&matched));
        }
    }

  /* Also check deploys. In case remote-delete --force is run, we can end up
   * with a deploy without a corresponding ref in the repo. */
  flatpak_dir_collect_deployed_refs (self, kind_str, id_prefix, arch, branch, matches, NULL, NULL);

  return matches;
}

Project: flatpak
File: flatpak-dir.c

get_summary_for_ref (FlatpakRemoteState *self,
                     const char *ref)
{
  GVariant *summary = NULL;

  if (self->index != NULL)
    {
      g_autofree char * arch = flatpak_get_arch_for_ref (ref);

      if (arch != NULL)
        summary = g_hash_table_lookup (self->subsummaries, arch);

      if (summary == NULL)
        {
          const char *non_compat_arch = flatpak_get_compat_arch_reverse (arch);

          if (non_compat_arch != NULL)
            summary = g_hash_table_lookup (self->subsummaries, non_compat_arch);
        }
    }
  else
    summary = self->summary;

  return summary;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_find_installed_ref (FlatpakDir   *self,
                                const char   *opt_name,
                                const char   *opt_branch,
                                const char   *opt_arch,
                                FlatpakKinds  kinds,
                                GError      **error)
{
  g_autoptr(FlatpakDecomposed) local_ref = NULL;
  g_autoptr(GHashTable) local_refs = NULL;
  g_autoptr(GError) my_error = NULL;

  local_refs = flatpak_dir_get_all_installed_refs (self, kinds, error);
  if (local_refs == NULL)
    return NULL;

  local_ref = find_matching_ref (local_refs, opt_name, opt_branch, NULL,
                                 opt_arch, kinds, &my_error);
  if (local_ref == NULL)
    {
      if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
        g_clear_error (&my_error);
      else
        {
          g_propagate_error (error, g_steal_pointer (&my_error));
          return NULL;
        }
    }
  else
    {
      return g_steal_pointer (&local_ref);
    }

  g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED,
               _("%s/%s/%s not installed"),
               opt_name ? opt_name : "*unspecified*",
               opt_arch ? opt_arch : "*unspecified*",
               opt_branch ? opt_branch : "*unspecified*");
  return NULL;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_delete_mirror_refs (FlatpakDir    *self,
                                gboolean       dry_run,
                                GCancellable  *cancellable,
                                GError       **error)
{
  g_autoptr(GHashTable) collection_refs = NULL;  /* (element-type OstreeCollectionRef utf8) */
  g_autoptr(GPtrArray) ignore_collections = g_ptr_array_new_with_free_func (g_free); /* (element-type utf8) */
  g_auto(GStrv) remotes = NULL;
  const char *repo_collection_id;
  OstreeRepo *repo;
  int i;

  /* Generally a flatpak repo should not have its own collection ID set, but
   * check just in case flatpak is being run on a server for some reason. When
   * a repo has a collection ID set, its own refs from refs/heads/ will be
   * listed by the ostree_repo_list_collection_refs() call below, and we need
   * to be sure not to delete them. There would be no reason to install from a
   * server to itself, so we don't expect refs matching repo_collection_id to
   * be in refs/mirrors/.
   */
  repo = flatpak_dir_get_repo (self);
  repo_collection_id = ostree_repo_get_collection_id (repo);
  if (repo_collection_id != NULL)
    g_ptr_array_add (ignore_collections, g_strdup (repo_collection_id));

  /* Check also for any disabled remotes and ignore any associated
   * collection-refs; in the case of Endless this would be the remote used for
   * OS updates which Flatpak shouldn't touch.
   */
  remotes = ostree_repo_remote_list (repo, NULL);
  for (i = 0; remotes != NULL && remotes[i] != NULL; i++)
    {
      g_autofree char *remote_collection_id = NULL;

      if (!flatpak_dir_get_remote_disabled (self, remotes[i]))
        continue;
      remote_collection_id = flatpak_dir_get_remote_collection_id (self, remotes[i]);
      if (remote_collection_id != NULL)
        g_ptr_array_add (ignore_collections, g_steal_pointer (&remote_collection_id));
    }
  g_ptr_array_add (ignore_collections, NULL);

  if (!ostree_repo_list_collection_refs (repo, NULL, &collection_refs,
                                         OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES,
                                         cancellable, error))
    return FALSE;

  /* Now delete any collection-refs which are in refs/mirrors/, were created by
   * Flatpak, and don't belong to a disabled remote.
   */
  GLNX_HASH_TABLE_FOREACH (collection_refs, const OstreeCollectionRef *, c_r)
    {
      if (g_strv_contains ((const char * const *)ignore_collections->pdata, c_r->collection_id))
        {
          g_debug ("Ignoring collection-ref (%s, %s) since its remote is disabled or it matches the repo collection ID",
                   c_r->collection_id, c_r->ref_name);
          continue;
        }

      /* Only delete refs which Flatpak created; the repo may have other
       * users. We could check only for refs that come from configured
       * remotes, but that would not cover the case of if a remote was
       * deleted.
       */
      if (g_str_has_prefix (c_r->ref_name, "app/") ||
          g_str_has_prefix (c_r->ref_name, "runtime/") ||
          g_str_has_prefix (c_r->ref_name, "appstream/") ||
          g_str_has_prefix (c_r->ref_name, "appstream2/") ||
          g_strcmp0 (c_r->ref_name, OSTREE_REPO_METADATA_REF) == 0)
        {
          if (dry_run)
            g_print (_("Skipping deletion of mirror ref (%s, %s)…\n"), c_r->collection_id, c_r->ref_name);
          else
            {
              if (!ostree_repo_set_collection_ref_immediate (repo, c_r, NULL, cancellable, error))
                return FALSE;
            }
        }
    }

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

  (flatpak_dir_log) (FlatpakDir * self,
                     const char *file,
                     int line,
                     const char *func,
                     const char *source, /* overrides self->name */
                     const char *change,
                     const char *remote,
                     const char *ref,
                     const char *commit,
                     const char *old_commit,
                     const char *url,
                     const char *format,
                     ...)
{
#ifdef HAVE_LIBSYSTEMD
  const char *installation = source ? source : flatpak_dir_get_name_cached (self);
  pid_t source_pid = flatpak_dir_get_source_pid (self);
  char message[1024];
  int len;
  va_list args;

  len = g_snprintf (message, sizeof (message), "%s: ", installation);

  va_start (args, format);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
  g_vsnprintf (message + len, sizeof (message) - len, format, args);
#pragma GCC diagnostic pop

  va_end (args);

  /* See systemd.journal-fields(7) for the meaning of the
   * standard fields we use, in particular OBJECT_PID
   */
  sd_journal_send ("MESSAGE_ID=" FLATPAK_MESSAGE_ID,
                   "PRIORITY=5",
                   "OBJECT_PID=%d", source_pid,
                   "CODE_FILE=%s", file,
                   "CODE_LINE=%d", line,
                   "CODE_FUNC=%s", func,
                   "MESSAGE=%s", message,
                   /* custom fields below */
                   "FLATPAK_VERSION=" PACKAGE_VERSION,
                   "INSTALLATION=%s", installation,
                   "OPERATION=%s", change,
                   "REMOTE=%s", remote ? remote : "",
                   "REF=%s", ref ? ref : "",
                   "COMMIT=%s", commit ? commit : "",
                   "OLD_COMMIT=%s", old_commit ? old_commit : "",
                   "URL=%s", url ? url : "",
                   NULL);
#endif
}

Project: flatpak
File: flatpak-dir.c

_flatpak_uri_equal (const char *uri1,
                    const char *uri2)
{
  g_autofree char *uri1_norm = NULL;
  g_autofree char *uri2_norm = NULL;
  gsize uri1_len = strlen (uri1);
  gsize uri2_len = strlen (uri2);

  /* URIs handled by libostree are equivalent with or without a trailing slash,
   * but this isn't otherwise guaranteed to be the case.
   */
  if (g_str_has_prefix (uri1, "oci+") || g_str_has_prefix (uri2, "oci+"))
    return g_strcmp0 (uri1, uri2) == 0;

  if (g_str_has_suffix (uri1, "/"))
    uri1_norm = g_strndup (uri1, uri1_len - 1);
  else
    uri1_norm = g_strdup (uri1);

  if (g_str_has_suffix (uri2, "/"))
    uri2_norm = g_strndup (uri2, uri2_len - 1);
  else
    uri2_norm = g_strdup (uri2);

  return g_strcmp0 (uri1_norm, uri2_norm) == 0;
}

Project: flatpak
File: flatpak-dir.c

add_related (FlatpakDir        *self,
             GPtrArray         *related,
             const char        *extension,
             FlatpakDecomposed *extension_ref,
             const char        *checksum,
             gboolean           no_autodownload,
             const char        *download_if,
             const char        *autoprune_unless,
             gboolean           autodelete,
             gboolean           locale_subset)
{
  g_autoptr(GBytes) deploy_data = NULL;
  g_autofree const char **old_subpaths = NULL;
  g_autofree const char *id = NULL;
  g_autofree const char *arch = NULL;
  g_autofree const char *branch = NULL;
  g_auto(GStrv) extra_subpaths = NULL;
  g_auto(GStrv) subpaths = NULL;
  FlatpakRelated *rel;
  gboolean download;
  gboolean delete = autodelete;
  gboolean auto_prune = FALSE;
  g_autoptr(GFile) unmaintained_path = NULL;

  deploy_data = flatpak_dir_get_deploy_data (self, extension_ref, FLATPAK_DEPLOY_VERSION_ANY, NULL, NULL);

  id = flatpak_decomposed_dup_id (extension_ref);
  arch = flatpak_decomposed_dup_arch (extension_ref);
  branch = flatpak_decomposed_dup_branch (extension_ref);

  if (deploy_data)
    old_subpaths = flatpak_deploy_data_get_subpaths (deploy_data);

  /* Only respect no-autodownload/download-if for uninstalled refs, we
     always want to update if you manually installed something */
  download =
    flatpak_extension_matches_reason (id, download_if, !no_autodownload) ||
    deploy_data != NULL;

  if (!flatpak_extension_matches_reason (id, autoprune_unless, TRUE))
    auto_prune = TRUE;

  /* Don't download if there is an unmaintained extension already installed */
  unmaintained_path =
    flatpak_find_unmaintained_extension_dir_if_exists (id, arch, branch, NULL);
  if (unmaintained_path != NULL && deploy_data == NULL)
    {
      g_debug ("Skipping related extension ‘%s’ because it is already "
               "installed as an unmaintained extension in ‘%s’.",
               id, flatpak_file_get_path_cached (unmaintained_path));
      download = FALSE;
    }

  if (g_str_has_suffix (extension, ".Debug"))
    {
      /* debug files only updated if already installed */
      if (deploy_data == NULL)
        download = FALSE;

      /* Always remove debug */
      delete = TRUE;
    }

  if (g_str_has_suffix (extension, ".Locale"))
    locale_subset = TRUE;

  if (locale_subset)
    {
      extra_subpaths = flatpak_dir_get_locale_subpaths (self);

      /* Always remove locale */
      delete = TRUE;
    }

  subpaths = flatpak_subpaths_merge ((char **) old_subpaths, extra_subpaths);

  rel = g_new0 (FlatpakRelated, 1);
  rel->ref = flatpak_decomposed_ref (extension_ref);
  rel->commit = g_strdup (checksum);
  rel->subpaths = g_steal_pointer (&subpaths);
  rel->download = download;
  rel->delete = delete;
  rel->auto_prune = auto_prune;

  g_ptr_array_add (related, rel);
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_find_remote_related_for_metadata (FlatpakDir         *self,
                                              FlatpakRemoteState *state,
                                              FlatpakDecomposed  *ref,
                                              GKeyFile           *metakey,
                                              GCancellable       *cancellable,
                                              GError            **error)
{
  int i;
  g_autoptr(GPtrArray) related = g_ptr_array_new_with_free_func ((GDestroyNotify) flatpak_related_free);
  g_autofree char *url = NULL;
  g_auto(GStrv) groups = NULL;
  g_autoptr(GRegex) masked = NULL;
  g_autofree char *ref_arch = flatpak_decomposed_dup_arch (ref);
  g_autofree char *ref_branch = flatpak_decomposed_dup_branch (ref);

  if (!ostree_repo_remote_get_url (self->repo,
                                   state->remote_name,
                                   &url,
                                   error))
    return NULL;

  if (*url == 0)
    return g_steal_pointer (&related);  /* Empty url, silently disables updates */

  masked = flatpak_dir_get_mask_regexp (self);

  groups = g_key_file_get_groups (metakey, NULL);
  for (i = 0; groups[i] != NULL; i++)
    {
      char *tagged_extension;

      if (g_str_has_prefix (groups[i], FLATPAK_METADATA_GROUP_PREFIX_EXTENSION) &&
          *(tagged_extension = (groups[i] + strlen (FLATPAK_METADATA_GROUP_PREFIX_EXTENSION))) != 0)
        {
          g_autofree char *extension = NULL;
          g_autofree char *version = g_key_file_get_string (metakey, groups[i],
                                                            FLATPAK_METADATA_KEY_VERSION, NULL);
          g_auto(GStrv) versions = g_key_file_get_string_list (metakey, groups[i],
                                                               FLATPAK_METADATA_KEY_VERSIONS,
                                                               NULL, NULL);
          gboolean subdirectories = g_key_file_get_boolean (metakey, groups[i],
                                                            FLATPAK_METADATA_KEY_SUBDIRECTORIES, NULL);
          gboolean no_autodownload = g_key_file_get_boolean (metakey, groups[i],
                                                             FLATPAK_METADATA_KEY_NO_AUTODOWNLOAD, NULL);
          g_autofree char *download_if = g_key_file_get_string (metakey, groups[i],
                                                                FLATPAK_METADATA_KEY_DOWNLOAD_IF, NULL);
          g_autofree char *autoprune_unless = g_key_file_get_string (metakey, groups[i],
                                                                     FLATPAK_METADATA_KEY_AUTOPRUNE_UNLESS, NULL);
          gboolean autodelete = g_key_file_get_boolean (metakey, groups[i],
                                                        FLATPAK_METADATA_KEY_AUTODELETE, NULL);
          gboolean locale_subset = g_key_file_get_boolean (metakey, groups[i],
                                                           FLATPAK_METADATA_KEY_LOCALE_SUBSET, NULL);
          const char *default_branches[] = { NULL, NULL};
          const char **branches;
          int branch_i;

          /* Parse actual extension name */
          flatpak_parse_extension_with_tag (tagged_extension, &extension, NULL);

          if (versions)
            branches = (const char **) versions;
          else
            {
              if (version)
                default_branches[0] = version;
              else
                default_branches[0] = ref_branch;
              branches = default_branches;
            }

          for (branch_i = 0; branches[branch_i] != NULL; branch_i++)
            {
              g_autoptr(FlatpakDecomposed) extension_ref = NULL;
              g_autofree char *checksum = NULL;
              const char *branch = branches[branch_i];

              extension_ref = flatpak_decomposed_new_from_parts (FLATPAK_KINDS_RUNTIME,
                                                                 extension, ref_arch, branch, NULL);
              if (extension_ref == NULL)
                continue;

              if (flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (extension_ref), &checksum, NULL, NULL, NULL, NULL))
                {
                  if (flatpak_filters_allow_ref (NULL, masked, flatpak_decomposed_get_ref (extension_ref)))
                    add_related (self, related, extension, extension_ref, checksum,
                                 no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
                }
              else if (subdirectories)
                {
                  g_autoptr(GPtrArray) subref_refs = flatpak_remote_state_match_subrefs (state, extension_ref);
                  for (int j = 0; j < subref_refs->len; j++)
                    {
                      FlatpakDecomposed *subref_ref = g_ptr_array_index (subref_refs, j);
                      g_autofree char *subref_checksum = NULL;

                      if (flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (subref_ref),
                                                           &subref_checksum, NULL, NULL, NULL, NULL) &&
                          flatpak_filters_allow_ref (NULL, masked,  flatpak_decomposed_get_ref (subref_ref)))
                        add_related (self, related, extension, subref_ref, subref_checksum,
                                     no_autodownload, download_if, autoprune_unless, autodelete, locale_subset);
                    }
                }
            }
        }
    }

  return g_steal_pointer (&related);
}

Project: flatpak
File: flatpak-dir.c

parse_ref_file (GKeyFile *keyfile,
                char    **name_out,
                char    **branch_out,
                char    **url_out,
                char    **title_out,
                GBytes  **gpg_data_out,
                gboolean *is_runtime_out,
                char    **collection_id_out,
                GError  **error)
{
  g_autofree char *url = NULL;
  g_autofree char *title = NULL;
  g_autofree char *name = NULL;
  g_autofree char *branch = NULL;
  g_autofree char *version = NULL;
  g_autoptr(GBytes) gpg_data = NULL;
  gboolean is_runtime = FALSE;
  g_autofree char *collection_id = NULL;
  g_autofree char *str = NULL;

  *name_out = NULL;
  *branch_out = NULL;
  *url_out = NULL;
  *title_out = NULL;
  *gpg_data_out = NULL;
  *is_runtime_out = FALSE;

  if (!g_key_file_has_group (keyfile, FLATPAK_REF_GROUP))
    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid file format, no %s group"), FLATPAK_REF_GROUP);

  version = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
                                   FLATPAK_REF_VERSION_KEY, NULL);
  if (version != NULL && strcmp (version, "1") != 0)
    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid version %s, only 1 supported"), version);

  url = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
                               FLATPAK_REF_URL_KEY, NULL);
  if (url == NULL)
    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid file format, no %s specified"), FLATPAK_REF_URL_KEY);

  name = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
                                FLATPAK_REF_NAME_KEY, NULL);
  if (name == NULL)
    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid file format, no %s specified"), FLATPAK_REF_NAME_KEY);

  branch = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
                                  FLATPAK_REF_BRANCH_KEY, NULL);
  if (branch == NULL)
    branch = g_strdup ("master");

  title = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
                                 FLATPAK_REF_TITLE_KEY, NULL);

  is_runtime = g_key_file_get_boolean (keyfile, FLATPAK_REF_GROUP,
                                       FLATPAK_REF_IS_RUNTIME_KEY, NULL);

  str = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
                               FLATPAK_REF_GPGKEY_KEY, NULL);
  if (str != NULL)
    {
      g_autofree guchar *decoded = NULL;
      gsize decoded_len;

      str = g_strstrip (str);
      decoded = g_base64_decode (str, &decoded_len);
      if (decoded_len < 10) /* Check some minimal size so we don't get crap */
        return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid file format, gpg key invalid"));

      gpg_data = g_bytes_new_take (g_steal_pointer (&decoded), decoded_len);
    }

  collection_id = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
                                         FLATPAK_REF_DEPLOY_COLLECTION_ID_KEY, NULL);

  if (collection_id == NULL || *collection_id == '\0')
    {
      collection_id = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP,
                                             FLATPAK_REF_COLLECTION_ID_KEY, NULL);
    }

  if (collection_id != NULL && *collection_id == '\0')
    collection_id = NULL;

  if (collection_id != NULL && gpg_data == NULL)
    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Collection ID requires GPG key to be provided"));

  *name_out = g_steal_pointer (&name);
  *branch_out = g_steal_pointer (&branch);
  *url_out = g_steal_pointer (&url);
  *title_out = g_steal_pointer (&title);
  *gpg_data_out = g_steal_pointer (&gpg_data);
  *is_runtime_out = is_runtime;
  *collection_id_out = g_steal_pointer (&collection_id);

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_list_remote_refs (FlatpakDir         *self,
                              FlatpakRemoteState *state,
                              GHashTable        **refs,
                              GCancellable       *cancellable,
                              GError            **error)
{
  g_autoptr(GError) my_error = NULL;

  if (error == NULL)
    error = &my_error;

  if (!flatpak_dir_list_all_remote_refs (self, state, refs,
                                         cancellable, error))
    return FALSE;

  if (flatpak_dir_get_remote_noenumerate (self, state->remote_name))
    {
      g_autoptr(GHashTable) decomposed_local_refs =
        g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, NULL);
      g_autoptr(GHashTable) local_refs = NULL;
      GHashTableIter hash_iter;
      gpointer key;
      g_autofree char *refspec_prefix = g_strconcat (state->remote_name, ":.", NULL);

      /* For noenumerate remotes, only return data for already locally
       * available refs */

      if (!ostree_repo_list_refs (self->repo, refspec_prefix, &local_refs,
                                  cancellable, error))
        return FALSE;

      g_hash_table_iter_init (&hash_iter, local_refs);
      while (g_hash_table_iter_next (&hash_iter, &key, NULL))
        {
          const char *refspec = key;
          g_autoptr(FlatpakDecomposed) d = flatpak_decomposed_new_from_refspec (refspec, NULL);
          if (d)
            g_hash_table_insert (decomposed_local_refs, g_steal_pointer (&d), NULL);
        }

      /* Then we remove all remote refs not in the local refs set */
      g_hash_table_foreach_remove (*refs,
                                   remove_unless_decomposed_in_hash,
                                   decomposed_local_refs);
    }

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_export_dir (GFile        *source,
                    GFile        *destination,
                    const char   *symlink_prefix,
                    GCancellable *cancellable,
                    GError      **error)
{
  const char *exported_subdirs[] = {
    "share/applications",                  "../..",
    "share/icons",                         "../..",
    "share/dbus-1/services",               "../../..",
    "share/gnome-shell/search-providers",  "../../..",
    "share/mime/packages",                 "../../..",
    "bin",                                 "..",
  };
  int i;

  for (i = 0; i < G_N_ELEMENTS (exported_subdirs); i = i + 2)
    {
      /* The fds are closed by this call */
      g_autoptr(GFile) sub_source = g_file_resolve_relative_path (source, exported_subdirs[i]);
      g_autoptr(GFile) sub_destination = g_file_resolve_relative_path (destination, exported_subdirs[i]);
      g_autofree char *sub_symlink_prefix = g_build_filename (exported_subdirs[i + 1], symlink_prefix, exported_subdirs[i], NULL);

      if (!g_file_query_exists (sub_source, cancellable))
        continue;

      if (!flatpak_mkdir_p (sub_destination, cancellable, error))
        return FALSE;

      if (!export_dir (AT_FDCWD, flatpak_file_get_path_cached (sub_source), sub_symlink_prefix, "",
                       AT_FDCWD, flatpak_file_get_path_cached (sub_destination),
                       cancellable, error))
        return FALSE;
    }

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

apply_extra_data (FlatpakDir   *self,
                  GFile        *checkoutdir,
                  GCancellable *cancellable,
                  GError      **error)
{
  g_autoptr(GFile) metadata = NULL;
  g_autofree char *metadata_contents = NULL;
  gsize metadata_size;
  g_autoptr(GKeyFile) metakey = NULL;
  g_autofree char *id = NULL;
  g_autofree char *runtime_pref = NULL;
  g_autoptr(FlatpakDecomposed) runtime_ref = NULL;
  g_autoptr(FlatpakDeploy) runtime_deploy = NULL;
  g_autoptr(FlatpakBwrap) bwrap = NULL;
  g_autoptr(GFile) app_files = NULL;
  g_autoptr(GFile) apply_extra_file = NULL;
  g_autoptr(GFile) app_export_file = NULL;
  g_autoptr(GFile) extra_export_file = NULL;
  g_autoptr(GFile) extra_files = NULL;
  g_autoptr(GFile) runtime_files = NULL;
  g_autoptr(FlatpakContext) app_context = NULL;
  g_auto(GStrv) minimal_envp = NULL;
  g_autofree char *runtime_arch = NULL;
  int exit_status;
  const char *group = FLATPAK_METADATA_GROUP_APPLICATION;
  g_autoptr(GError) local_error = NULL;

  apply_extra_file = g_file_resolve_relative_path (checkoutdir, "files/bin/apply_extra");
  if (!g_file_query_exists (apply_extra_file, cancellable))
    return TRUE;

  metadata = g_file_get_child (checkoutdir, "metadata");

  if (!g_file_load_contents (metadata, cancellable, &metadata_contents, &metadata_size, NULL, error))
    return FALSE;

  metakey = g_key_file_new ();
  if (!g_key_file_load_from_data (metakey, metadata_contents, metadata_size, 0, error))
    return FALSE;

  id = g_key_file_get_string (metakey, group, FLATPAK_METADATA_KEY_NAME,
                              &local_error);
  if (id == NULL)
    {
      group = FLATPAK_METADATA_GROUP_RUNTIME;
      id = g_key_file_get_string (metakey, group, FLATPAK_METADATA_KEY_NAME,
                                  NULL);
      if (id == NULL)
        {
          g_propagate_error (error, g_steal_pointer (&local_error));
          return FALSE;
        }
      g_clear_error (&local_error);
    }

  runtime_pref = g_key_file_get_string (metakey, group,
                                        FLATPAK_METADATA_KEY_RUNTIME, error);
  if (runtime_pref == NULL)
    runtime_pref = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_EXTENSION_OF,
                                          FLATPAK_METADATA_KEY_RUNTIME, NULL);
  if (runtime_pref == NULL)
    return FALSE;

  runtime_ref = flatpak_decomposed_new_from_pref (FLATPAK_KINDS_RUNTIME, runtime_pref, error);
  if (runtime_ref == NULL)
    return FALSE;
  runtime_arch = flatpak_decomposed_dup_arch (runtime_ref);

  if (!g_key_file_get_boolean (metakey, FLATPAK_METADATA_GROUP_EXTRA_DATA,
                               FLATPAK_METADATA_KEY_NO_RUNTIME, NULL))
    {
      /* We pass in self here so that we ensure that we find the runtime in case it only
         exists in this installation (which might be custom) */
      runtime_deploy = flatpak_find_deploy_for_ref (flatpak_decomposed_get_ref (runtime_ref), NULL, self, cancellable, error);
      if (runtime_deploy == NULL)
        return FALSE;
      runtime_files = flatpak_deploy_get_files (runtime_deploy);
    }

  app_files = g_file_get_child (checkoutdir, "files");
  app_export_file = g_file_get_child (checkoutdir, "export");
  extra_files = g_file_get_child (app_files, "extra");
  extra_export_file = g_file_get_child (extra_files, "export");

  minimal_envp = flatpak_run_get_minimal_env (FALSE, FALSE);
  bwrap = flatpak_bwrap_new (minimal_envp);
  flatpak_bwrap_add_args (bwrap, flatpak_get_bwrap (), NULL);

  if (runtime_files)
    flatpak_bwrap_add_args (bwrap,
                            "--ro-bind", flatpak_file_get_path_cached (runtime_files), "/usr",
                            "--lock-file", "/usr/.ref",
                            NULL);

  flatpak_bwrap_add_args (bwrap,
                          "--ro-bind", flatpak_file_get_path_cached (app_files), "/app",
                          "--bind", flatpak_file_get_path_cached (extra_files), "/app/extra",
                          "--chdir", "/app/extra",
                          /* We run as root in the system-helper case, so drop all caps */
                          "--cap-drop", "ALL",
                          NULL);

  if (!flatpak_run_setup_base_argv (bwrap, runtime_files, NULL, runtime_arch,
                                    /* Might need multiarch in apply_extra (see e.g. #3742). Should be pretty safe in this limited context */
                                    FLATPAK_RUN_FLAG_MULTIARCH |
                                    FLATPAK_RUN_FLAG_NO_SESSION_HELPER | FLATPAK_RUN_FLAG_NO_PROC,
                                    error))
    return FALSE;

  app_context = flatpak_context_new ();

  if (!flatpak_run_add_environment_args (bwrap, NULL,
                                         FLATPAK_RUN_FLAG_NO_SESSION_BUS_PROXY |
                                         FLATPAK_RUN_FLAG_NO_SYSTEM_BUS_PROXY |
                                         FLATPAK_RUN_FLAG_NO_A11Y_BUS_PROXY,
                                         id,
                                         app_context, NULL, NULL, NULL, cancellable, error))
    return FALSE;

  flatpak_bwrap_envp_to_args (bwrap);

  flatpak_bwrap_add_arg (bwrap, "/app/bin/apply_extra");

  flatpak_bwrap_finish (bwrap);

  g_debug ("Running /app/bin/apply_extra ");

  /* We run the sandbox without caps, but it can still create files owned by itself with
   * arbitrary permissions, including setuid myself. This is extra risky in the case where
   * this runs as root in the system helper case. We canonicalize the permissions at the
   * end, but to avoid non-canonical permissions leaking out before then we make the
   * toplevel dir only accessible to the user */
  if (chmod (flatpak_file_get_path_cached (extra_files), 0700) != 0)
    {
      glnx_set_error_from_errno (error);
      return FALSE;
    }

  if (!g_spawn_sync (NULL,
                     (char **) bwrap->argv->pdata,
                     bwrap->envp,
                     G_SPAWN_SEARCH_PATH,
                     child_setup, bwrap->fds,
                     NULL, NULL,
                     &exit_status,
                     error))
    return FALSE;

  if (!flatpak_canonicalize_permissions (AT_FDCWD, flatpak_file_get_path_cached (extra_files),
                                         getuid () == 0 ? 0 : -1,
                                         getuid () == 0 ? 0 : -1,
                                         error))
    return FALSE;

  if (exit_status != 0)
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                   _("apply_extra script failed, exit status %d"), exit_status);
      return FALSE;
    }

  if (g_file_query_exists (extra_export_file, cancellable))
    {
      if (!flatpak_mkdir_p (app_export_file, cancellable, error))
        return FALSE;
      if (!flatpak_cp_a (extra_export_file,
                         app_export_file,
                         FLATPAK_CP_FLAGS_MERGE,
                         cancellable, error))
        return FALSE;
    }

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_find_installed_refs (FlatpakDir           *self,
                                 const char           *opt_name,
                                 const char           *opt_branch,
                                 const char           *opt_arch,
                                 FlatpakKinds          kinds,
                                 FindMatchingRefsFlags flags,
                                 GError              **error)
{
  g_autoptr(GHashTable) local_refs = NULL;
  g_autoptr(GPtrArray) matched_refs = NULL;

  local_refs = flatpak_dir_get_all_installed_refs (self, kinds, error);
  if (local_refs == NULL)
    return NULL;

  matched_refs = find_matching_refs (local_refs,
                                     opt_name,
                                     opt_branch,
                                     NULL, /* default branch */
                                     opt_arch,
                                     NULL, /* default arch */
                                     kinds,
                                     flags,
                                     error);
  if (matched_refs == NULL)
    return NULL;

  return g_steal_pointer (&matched_refs);
}

Project: flatpak
File: flatpak-dir.c

flatpak_remote_state_lookup_sparse_cache (FlatpakRemoteState *self,
                                          const char         *ref,
                                          VarMetadataRef     *out_metadata,
                                          GError            **error)
{
  VarSummaryRef summary;
  VarMetadataRef meta;
  VarVariantRef sparse_cache_v;
  guint32 summary_version;
  GVariant *summary_v;

  if (!flatpak_remote_state_ensure_summary (self, error))
    return FALSE;

  summary_v = get_summary_for_ref (self, ref);
  if (summary_v == NULL)
    return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
                             _("No entry for %s in remote summary flatpak sparse cache "), ref);

  summary = var_summary_from_gvariant (summary_v);
  meta = var_summary_get_metadata (summary);

  summary_version = GUINT32_FROM_LE (var_metadata_lookup_uint32 (meta, "xa.summary-version", 0));

  if (summary_version == 0)
    {
      if (var_metadata_lookup (meta, "xa.sparse-cache", NULL, &sparse_cache_v))
        {
          VarSparseCacheRef sparse_cache = var_sparse_cache_from_variant (sparse_cache_v);
          if (var_sparse_cache_lookup (sparse_cache, ref, NULL, out_metadata))
            return TRUE;
        }
    }
  else if (summary_version == 1)
    {
      VarRefMapRef ref_map = var_summary_get_ref_map (summary);
      VarRefInfoRef info;

      if (flatpak_var_ref_map_lookup_ref (ref_map, ref, &info))
        {
          *out_metadata = var_ref_info_get_metadata (info);
          return TRUE;
        }
    }
  else
    {
      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Unsupported summary version %d for remote %s"),
                          summary_version, self->remote_name);
      return FALSE;
    }

  return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
                             _("No entry for %s in remote summary flatpak sparse cache "), ref);
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_find_remote_by_uri (FlatpakDir *self,
                                const char *uri)
{
  g_auto(GStrv) remotes = NULL;

  g_return_val_if_fail (self != NULL, NULL);
  g_return_val_if_fail (uri != NULL, NULL);

  if (!flatpak_dir_ensure_repo (self, NULL, NULL))
    return NULL;

  remotes = flatpak_dir_list_enumerated_remotes (self, NULL, NULL);
  if (remotes)
    {
      int i;

      for (i = 0; remotes != NULL && remotes[i] != NULL; i++)
        {
          const char *remote = remotes[i];
          g_autofree char *remote_uri = NULL;

          if (!ostree_repo_remote_get_url (self->repo,
                                           remote,
                                           &remote_uri,
                                           NULL))
            continue;

          if (_flatpak_uri_equal (uri, remote_uri))
            return g_strdup (remote);
        }
    }

  return NULL;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_system_helper_call (FlatpakDir         *self,
                                const gchar        *method_name,
                                GVariant           *parameters,
                                const GVariantType *reply_type,
                                GUnixFDList       **out_fd_list,
                                GCancellable       *cancellable,
                                GError            **error)
{
  GVariant *res;

  if (g_once_init_enter (&self->system_helper_bus))
    {
      const char *on_session = g_getenv ("FLATPAK_SYSTEM_HELPER_ON_SESSION");
      GDBusConnection *system_helper_bus =
        g_bus_get_sync (on_session != NULL ? G_BUS_TYPE_SESSION : G_BUS_TYPE_SYSTEM,
                        cancellable, NULL);

      /* To ensure reverse mapping */
      flatpak_error_quark ();

      g_once_init_leave (&self->system_helper_bus, system_helper_bus ? system_helper_bus : (gpointer) 1 );
    }

  if (self->system_helper_bus == (gpointer) 1)
    {
      flatpak_fail (error, _("Unable to connect to system bus"));
      return NULL;
    }

  g_debug ("Calling system helper: %s", method_name);
  res = g_dbus_connection_call_with_unix_fd_list_sync (self->system_helper_bus,
                                                       "org.freedesktop.Flatpak.SystemHelper",
                                                       "/org/freedesktop/Flatpak/SystemHelper",
                                                       "org.freedesktop.Flatpak.SystemHelper",
                                                       method_name,
                                                       parameters,
                                                       reply_type,
                                                       G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
                                                       NULL, out_fd_list,
                                                       cancellable,
                                                       error);

 if (res == NULL && error)
    g_dbus_error_strip_remote_error (*error);

  return res;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_find_remote_related (FlatpakDir         *self,
                                 FlatpakRemoteState *state,
                                 FlatpakDecomposed  *ref,
                                 GCancellable       *cancellable,
                                 GError            **error)
{
  g_autofree char *metadata = NULL;
  g_autoptr(GKeyFile) metakey = g_key_file_new ();
  g_autoptr(GPtrArray) related = g_ptr_array_new_with_free_func ((GDestroyNotify) flatpak_related_free);
  g_autofree char *url = NULL;

  if (!ostree_repo_remote_get_url (self->repo,
                                   state->remote_name,
                                   &url,
                                   error))
    return NULL;

  if (*url == 0)
    return g_steal_pointer (&related);  /* Empty url, silently disables updates */

  if (flatpak_remote_state_load_data (state, flatpak_decomposed_get_ref (ref),
                                      NULL, NULL, &metadata,
                                      NULL) &&
      g_key_file_load_from_data (metakey, metadata, -1, 0, NULL))
    {
      g_ptr_array_unref (related);
      related = flatpak_dir_find_remote_related_for_metadata (self, state, ref, metakey, cancellable, error);
    }

  return g_steal_pointer (&related);
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_remote_fetch_summary_index (FlatpakDir   *self,
                                        const char   *name_or_uri,
                                        gboolean      only_cached,
                                        GBytes      **out_index,
                                        GBytes      **out_index_sig,
                                        GCancellable *cancellable,
                                        GError      **error)
{
  g_autofree char *url = NULL;
  gboolean is_local;
  g_autoptr(GError) local_error = NULL;
  g_autoptr(GError) cache_error = NULL;
  g_autoptr(GBytes) cached_index = NULL;
  g_autoptr(GBytes) cached_index_sig = NULL;
  g_autoptr(GBytes) index = NULL;
  g_autoptr(GBytes) index_sig = NULL;
  gboolean gpg_verify_summary;

  ensure_soup_session (self);

  if (!ostree_repo_remote_get_url (self->repo, name_or_uri, &url, error))
    return FALSE;

  if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, name_or_uri, &gpg_verify_summary, NULL))
    return FALSE;

  if (!g_str_has_prefix (name_or_uri, "file:") && flatpak_dir_get_remote_disabled (self, name_or_uri))
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
                   "Can't fetch summary from disabled remote ‘%s’", name_or_uri);
      return FALSE;
    }

  if (flatpak_dir_get_remote_oci (self, name_or_uri))
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                   "No index in OCI remote ‘%s’", name_or_uri);
      return FALSE;
    }

  is_local = g_str_has_prefix (url, "file:");

  /* Seems ostree asserts if this is NULL */
  if (error == NULL)
    error = &local_error;

  flatpak_dir_remote_load_cached_summary (self, name_or_uri, ".idx", ".idx.sig",
                                          &cached_index, &cached_index_sig, cancellable, &cache_error);

  if (only_cached)
    {
      if (cached_index == NULL)
        {
          g_propagate_error (error, g_steal_pointer (&cache_error));
          return FALSE;
        }
      g_debug ("Loaded summary index from cache for remote ‘%s’", name_or_uri);

      index = g_steal_pointer (&cached_index);
      if (gpg_verify_summary)
        index_sig = g_steal_pointer (&cached_index_sig);
    }
  else
    {
      g_autofree char *index_url = g_build_filename (url, "summary.idx", NULL);
      g_autoptr(GBytes) dl_index = NULL;
      gboolean used_download = FALSE;

      g_debug ("Fetching summary index file for remote ‘%s’", name_or_uri);

      dl_index = flatpak_load_uri (self->soup_session, index_url, 0, NULL,
                                   NULL, NULL, NULL,
                                   cancellable, error);
      if (dl_index == NULL)
        return FALSE;

      /* If the downloaded index is the same as the cached one we need not re-download or
       * re-verify, just use the cache (which we verified before) */
      if (cached_index != NULL && g_bytes_equal (cached_index, dl_index))
        {
          index = g_steal_pointer (&cached_index);
          if (gpg_verify_summary)
            index_sig = g_steal_pointer (&cached_index_sig);
        }
      else
        {
          index = g_steal_pointer (&dl_index);
          used_download = TRUE;
        }

      if (gpg_verify_summary && index_sig == NULL)
        {
          g_autofree char *index_digest = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, index);
          g_autofree char *index_sig_filename = g_strconcat (index_digest, ".idx.sig", NULL);
          g_autofree char *index_sig_url = g_build_filename (url, "summaries", index_sig_filename, NULL);
          g_autofree char *index_sig_url2 = g_build_filename (url, "summary.idx.sig", NULL);
          g_autoptr(GError) dl_sig_error = NULL;
          g_autoptr (GBytes) dl_index_sig = NULL;

          dl_index_sig = load_uri_with_fallback (self->soup_session, index_sig_url, index_sig_url2, 0, NULL,
                                                 cancellable, &dl_sig_error);
          if (dl_index_sig == NULL)
            {
              if (g_error_matches (dl_sig_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
                g_set_error (error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE,
                             "GPG verification enabled, but no summary signatures found (use gpg-verify-summary=false in remote config to disable)");
              else
                g_propagate_error (error, g_steal_pointer (&dl_sig_error));

              return FALSE;
            }

          if (!remote_verify_signature (self->repo, name_or_uri,
                                        index, dl_index_sig,
                                        cancellable, error))
            return FALSE;

          index_sig = g_steal_pointer (&dl_index_sig);
          used_download = TRUE;
        }

      g_assert (index != NULL);
      if (gpg_verify_summary)
        g_assert (index_sig != NULL);

      /* Update cache on disk if we downloaded anything, but never cache for file: repos */
      if (used_download && !is_local &&
          !flatpak_dir_remote_save_cached_summary (self, name_or_uri, ".idx", ".idx.sig",
                                                   index, index_sig, cancellable, error))
        return FALSE;
    }

  /* Cache in memory */
  if (!is_local && !only_cached)
    {
      g_autofree char *cache_key = g_strconcat ("index-", name_or_uri, NULL);
      flatpak_dir_cache_summary (self, index, index_sig, cache_key, url);
    }

  *out_index = g_steal_pointer (&index);
  if (out_index_sig)
    *out_index_sig = g_steal_pointer (&index_sig);

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

find_ref_for_refs_set (GHashTable   *refs,
                       const char   *name,
                       const char   *opt_branch,
                       const char   *opt_default_branch,
                       const char   *opt_arch,
                       FlatpakKinds  kinds,
                       GError      **error)
{
  g_autoptr(GError) my_error = NULL;
  g_autoptr(FlatpakDecomposed) ref = find_matching_ref (refs,
                                                        name,
                                                        opt_branch,
                                                        opt_default_branch,
                                                        opt_arch,
                                                        kinds,
                                                        &my_error);
  if (ref == NULL)
    {
      if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
        g_clear_error (&my_error);
      else
        {
          g_propagate_error (error, g_steal_pointer (&my_error));
          return NULL;
        }
    }
  else
    {
      return g_steal_pointer (&ref);
    }

  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
               _("Can't find ref %s%s%s%s%s"), name,
               (opt_arch != NULL || opt_branch != NULL) ? "/" : "",
               opt_arch ? opt_arch : "",
               opt_branch ? "/" : "",
               opt_branch ? opt_branch : "");

  return NULL;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_create_remote_for_ref_file (FlatpakDir         *self,
                                        GKeyFile           *keyfile,
                                        const char         *default_arch,
                                        char              **remote_name_out,
                                        char              **collection_id_out,
                                        FlatpakDecomposed **ref_out,
                                        GError            **error)
{
  g_autoptr(GBytes) gpg_data = NULL;
  g_autofree char *name = NULL;
  g_autofree char *branch = NULL;
  g_autofree char *url = NULL;
  g_autofree char *title = NULL;
  g_autofree char *remote = NULL;
  gboolean is_runtime = FALSE;
  g_autofree char *collection_id = NULL;
  g_autoptr(GFile) deploy_dir = NULL;
  g_autoptr(FlatpakDecomposed) ref = NULL;

  if (!parse_ref_file (keyfile, &name, &branch, &url, &title, &gpg_data, &is_runtime, &collection_id, error))
    return FALSE;

  ref = flatpak_decomposed_new_from_parts (is_runtime ? FLATPAK_KINDS_RUNTIME : FLATPAK_KINDS_APP,
                                           name, default_arch, branch, error);
  if (ref == NULL)
    return FALSE;

  deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, NULL);
  if (deploy_dir != NULL)
    {
      g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
                   is_runtime ? _("Runtime %s, branch %s is already installed") :
                   _("App %s, branch %s is already installed"),
                   name, branch);
      return FALSE;
    }

  /* First try to reuse existing remote */
  remote = flatpak_dir_find_remote_by_uri (self, url);

  if (remote == NULL)
    {
      remote = flatpak_dir_create_origin_remote (self, url, name, title, flatpak_decomposed_get_ref (ref),
                                                 gpg_data, collection_id, NULL, NULL, error);
      if (remote == NULL)
        return FALSE;
    }

  if (collection_id_out != NULL)
    *collection_id_out = g_steal_pointer (&collection_id);

  *remote_name_out = g_steal_pointer (&remote);
  *ref_out = g_steal_pointer (&ref);
  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

find_matching_refs (GHashTable           *refs,
                    const char           *opt_name,
                    const char           *opt_branch,
                    const char           *opt_default_branch,
                    const char           *opt_arch,
                    const char           *opt_default_arch,
                    FlatpakKinds          kinds,
                    FindMatchingRefsFlags flags,
                    GError              **error)
{
  g_autoptr(GPtrArray) matched_refs = NULL;
  const char **arches = flatpak_get_arches ();
  const char *opt_arches[] = {opt_arch, NULL};
  g_autoptr(GError) local_error = NULL;
  gboolean found_exact_name_match = FALSE;
  gboolean found_default_branch_match = FALSE;
  gboolean found_default_arch_match = FALSE;

  if (opt_arch != NULL)
    arches = opt_arches;

  if (opt_name && !(flags & FIND_MATCHING_REFS_FLAGS_FUZZY) &&
      !flatpak_is_valid_name (opt_name, -1, &local_error))
    {
      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("'%s' is not a valid name: %s"), opt_name, local_error->message);
      return NULL;
    }

  if (opt_branch && !flatpak_is_valid_branch (opt_branch, -1, &local_error))
    {
      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("'%s' is not a valid branch name: %s"), opt_branch, local_error->message);
      return NULL;
    }

  matched_refs = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref);

  GLNX_HASH_TABLE_FOREACH (refs, FlatpakDecomposed *, ref)
    {
      if ((flatpak_decomposed_get_kinds (ref) & kinds) == 0)
        continue;

      if (opt_name)
        {
          if ((flags & FIND_MATCHING_REFS_FLAGS_FUZZY) && !flatpak_decomposed_id_is_subref (ref))
            {
              if (!flatpak_decomposed_is_id_fuzzy (ref, opt_name))
                continue;
            }
          else
            {
              if (!flatpak_decomposed_is_id (ref, opt_name))
                continue;
           }
        }

      if (!flatpak_decomposed_is_arches (ref, -1, arches))
        continue;

      if (opt_branch != NULL && !flatpak_decomposed_is_branch (ref, opt_branch))
        continue;

      if (opt_name != NULL && flatpak_decomposed_is_id (ref, opt_name))
        found_exact_name_match = TRUE;

      if (opt_default_arch != NULL && flatpak_decomposed_is_arch (ref, opt_default_arch))
        found_default_arch_match = TRUE;

      if (opt_default_branch != NULL && flatpak_decomposed_is_branch (ref, opt_default_branch))
        found_default_branch_match = TRUE;

      g_ptr_array_add (matched_refs, flatpak_decomposed_ref (ref));
    }

  /* Don't show fuzzy matches if we found at least one exact name match, and
   * enforce the default arch/branch */
  if (found_exact_name_match || found_default_arch_match || found_default_branch_match)
    {
      guint i;

      /* Walk through the array backwards so we can safely remove */
      for (i = matched_refs->len; i > 0; i--)
        {
          FlatpakDecomposed *matched_ref = g_ptr_array_index (matched_refs, i - 1);

          if (found_exact_name_match && !flatpak_decomposed_is_id (matched_ref, opt_name))
            g_ptr_array_remove_index (matched_refs, i - 1);
          else if (found_default_arch_match && !flatpak_decomposed_is_arch (matched_ref, opt_default_arch))
            g_ptr_array_remove_index (matched_refs, i - 1);
          else if (found_default_branch_match && !flatpak_decomposed_is_branch (matched_ref, opt_default_branch))
            g_ptr_array_remove_index (matched_refs, i - 1);
        }
    }

  return g_steal_pointer (&matched_refs);
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_list_all_remote_refs (FlatpakDir         *self,
                                  FlatpakRemoteState *state,
                                  GHashTable        **out_all_refs,
                                  GCancellable       *cancellable,
                                  GError            **error)
{
  g_autoptr(GHashTable) ret_all_refs = NULL;
  VarSummaryRef summary;
  VarMetadataRef exts;
  VarRefMapRef ref_map;
  VarVariantRef v;

  /* This is  ref->commit */
  ret_all_refs = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, (GEqualFunc)flatpak_decomposed_equal, (GDestroyNotify)flatpak_decomposed_unref, g_free);

  if (state->index != NULL)
    {
      /* We're online, so report only the refs from the summary */
      GLNX_HASH_TABLE_FOREACH_KV (state->subsummaries, const char *, arch, GVariant *, subsummary)
        {
          summary = var_summary_from_gvariant (subsummary);
          ref_map = var_summary_get_ref_map (summary);
          populate_hash_table_from_refs_map (ret_all_refs, NULL, ref_map, NULL, state);
        }
    }
  else if (state->summary != NULL)
    {
      /* We're online, so report only the refs from the summary */
      const char *main_collection_id = NULL;

      summary = var_summary_from_gvariant (state->summary);

      exts = var_summary_get_metadata (summary);

      if (state->is_file_uri)
        {
          /* This is a local repo, generally this means we gave a file: uri to a sideload repo so
           * we can enumerate it. We special case this by also adding all the collection_ref maps,
           * with collection_id set on the decomposed refs and setting the right collection id for
           * the main ref_map.
           */
          main_collection_id = var_metadata_lookup_string (exts, "ostree.summary.collection-id", NULL);
          if (var_metadata_lookup (exts, "ostree.summary.collection-map", NULL, &v))
            {
              VarCollectionMapRef map = var_collection_map_from_variant (v);

              gsize len = var_collection_map_get_length (map);
              for (gsize i = 0; i < len; i++)
                {
                  VarCollectionMapEntryRef entry = var_collection_map_get_at (map, i);
                  const char *collection_id = var_collection_map_entry_get_key (entry);
                  ref_map = var_collection_map_entry_get_value (entry);

                  populate_hash_table_from_refs_map (ret_all_refs, NULL, ref_map, collection_id, state);
                }
            }
        }

      /* refs that match the main collection-id,
         NOTE: We only set collection id if this is a file: uri remote */
      ref_map = var_summary_get_ref_map (summary);
      populate_hash_table_from_refs_map (ret_all_refs, NULL, ref_map, main_collection_id, state);
    }
  else if (state->collection_id)
    {
      g_autoptr(GHashTable) ref_mtimes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

      /* No main summary, add just all sideloded refs, with the latest version of each checksum */

      for (int i = 0; i < state->sideload_repos->len; i++)
        {
          FlatpakSideloadState *ss = g_ptr_array_index (state->sideload_repos, i);

          summary = var_summary_from_gvariant (ss->summary);
          exts = var_summary_get_metadata (summary);

          if (var_metadata_lookup (exts, "ostree.summary.collection-map", NULL, &v))
            {
              VarCollectionMapRef map = var_collection_map_from_variant (v);

              if (var_collection_map_lookup (map, state->collection_id, NULL, &ref_map))
                populate_hash_table_from_refs_map (ret_all_refs, ref_mtimes, ref_map, NULL, state);
            }
        }
    }

  /* If no sideloaded refs, might as well return the summary error if set */
  if (g_hash_table_size (ret_all_refs) == 0 &&
      !flatpak_remote_state_ensure_summary (state, error))
    return FALSE;

  *out_all_refs = g_steal_pointer (&ret_all_refs);

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_remote_fetch_summary (FlatpakDir   *self,
                                  const char   *name_or_uri,
                                  gboolean      only_cached,
                                  GBytes      **out_summary,
                                  GBytes      **out_summary_sig,
                                  GCancellable *cancellable,
                                  GError      **error)
{
  g_autofree char *url = NULL;
  gboolean is_local;
  g_autoptr(GError) local_error = NULL;
  g_autoptr(GBytes) summary = NULL;
  g_autoptr(GBytes) summary_sig = NULL;

  if (!ostree_repo_remote_get_url (self->repo, name_or_uri, &url, error))
    return FALSE;

  if (!g_str_has_prefix (name_or_uri, "file:") && flatpak_dir_get_remote_disabled (self, name_or_uri))
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
                   "Can't fetch summary from disabled remote ‘%s’", name_or_uri);
      return FALSE;
    }

  is_local = g_str_has_prefix (url, "file:");

  /* Seems ostree asserts if this is NULL */
  if (error == NULL)
    error = &local_error;

  if (flatpak_dir_get_remote_oci (self, name_or_uri))
    {
      if (!flatpak_dir_remote_make_oci_summary (self, name_or_uri,
                                                only_cached,
                                                &summary,
                                                cancellable,
                                                error))
        return FALSE;
    }
  else
    {
      if (only_cached)
        {
          if (!flatpak_dir_remote_load_cached_summary (self, name_or_uri, NULL, ".sig",
                                                       &summary, &summary_sig, cancellable, error))
            return FALSE;
          g_debug ("Loaded summary from cache for remote ‘%s’", name_or_uri);
        }
      else
        {
          g_debug ("Fetching summary file for remote ‘%s’", name_or_uri);
          if (!ostree_repo_remote_fetch_summary (self->repo, name_or_uri,
                                                 &summary, &summary_sig,
                                                 cancellable,
                                                 error))
            return FALSE;
        }
    }

  if (summary == NULL)
    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Remote listing for %s not available; server has no summary file. Check the URL passed to remote-add was valid."), name_or_uri);

  if (!is_local && !only_cached)
    {
      g_autofree char *cache_key = g_strconcat ("summary-", name_or_uri, NULL);
      flatpak_dir_cache_summary (self, summary, summary_sig, cache_key, url);
    }

  *out_summary = g_steal_pointer (&summary);
  if (out_summary_sig)
    *out_summary_sig = g_steal_pointer (&summary_sig);

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_related_free (FlatpakRelated *self)
{
  flatpak_decomposed_unref (self->ref);
  g_free (self->commit);
  g_strfreev (self->subpaths);
  g_free (self);
}

Project: flatpak
File: flatpak-dir.c

find_matching_ref (GHashTable  *refs,
                   const char  *name,
                   const char  *opt_branch,
                   const char  *opt_default_branch,
                   const char  *opt_arch,
                   FlatpakKinds kinds,
                   GError     **error)
{
  const char **arches = flatpak_get_arches ();
  const char *opt_arches[] = {opt_arch, NULL};
  int i;

  if (opt_arch != NULL)
    arches = opt_arches;

  /* We stop at the first arch (in prio order) that has a match */
  for (i = 0; arches[i] != NULL; i++)
    {
      g_autoptr(GPtrArray) matched_refs = NULL;
      int j;

      matched_refs = find_matching_refs (refs,
                                         name,
                                         opt_branch,
                                         opt_default_branch,
                                         arches[i],
                                         NULL,
                                         kinds,
                                         FIND_MATCHING_REFS_FLAGS_NONE,
                                         error);
      if (matched_refs == NULL)
        return NULL;

      if (matched_refs->len == 0)
        continue;

      if (matched_refs->len == 1)
        return flatpak_decomposed_ref (g_ptr_array_index (matched_refs, 0));

      /* Nothing to do other than reporting the different choices */
      g_autoptr(GString) err = g_string_new ("");
      g_string_printf (err, _("Multiple branches available for %s, you must specify one of: "), name);
      g_ptr_array_sort (matched_refs, flatpak_strcmp0_ptr);
      for (j = 0; j < matched_refs->len; j++)
        {
          FlatpakDecomposed *ref = g_ptr_array_index (matched_refs, j);
          if (j != 0)
            g_string_append (err, ", ");

          const char *branch = flatpak_decomposed_get_branch (ref);

          g_string_append (err,
                           g_strdup_printf ("%s/%s/%s",
                                            name,
                                            opt_arch ? opt_arch : "",
                                            branch));
        }

      flatpak_fail (error, "%s", err->str);
      return NULL;
    }

  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
               _("Nothing matches %s"), name);
  return NULL;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_deploy_install (FlatpakDir        *self,
                            FlatpakDecomposed *ref,
                            const char        *origin,
                            const char       **subpaths,
                            const char       **previous_ids,
                            gboolean           reinstall,
                            GCancellable      *cancellable,
                            GError           **error)
{
  g_auto(GLnxLockFile) lock = { 0, };
  g_autoptr(GFile) deploy_base = NULL;
  g_autoptr(GFile) old_deploy_dir = NULL;
  gboolean created_deploy_base = FALSE;
  gboolean ret = FALSE;
  g_autoptr(GError) local_error = NULL;
  g_autofree char *remove_ref_from_remote = NULL;
  g_autofree char *commit = NULL;
  g_autofree char *old_active = NULL;

  if (!flatpak_dir_lock (self, &lock,
                         cancellable, error))
    goto out;

  old_deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable);
  if (old_deploy_dir != NULL)
    {
      old_active = flatpak_dir_read_active (self, ref, cancellable);

      if (reinstall)
        {
          g_autoptr(GBytes) old_deploy = NULL;
          const char *old_origin;

          old_deploy = flatpak_load_deploy_data (old_deploy_dir, ref, self->repo, FLATPAK_DEPLOY_VERSION_ANY, cancellable, error);
          if (old_deploy == NULL)
            goto out;

          /* If the old install was from a different remote, remove the ref */
          old_origin = flatpak_deploy_data_get_origin (old_deploy);
          if (strcmp (old_origin, origin) != 0)
            remove_ref_from_remote = g_strdup (old_origin);

          g_debug ("Removing old deployment for reinstall");
          if (!flatpak_dir_undeploy (self, ref, old_active,
                                     TRUE, FALSE,
                                     cancellable, error))
            goto out;
        }
      else
        {
          g_autofree char *id = flatpak_decomposed_dup_id (ref);
          g_autofree char *branch = flatpak_decomposed_dup_branch (ref);
          g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
                       _("%s branch %s already installed"), id, branch);
          goto out;
        }
    }

  deploy_base = flatpak_dir_get_deploy_dir (self, ref);
  if (!g_file_make_directory_with_parents (deploy_base, cancellable, &local_error))
    {
      if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
        {
          g_propagate_error (error, g_steal_pointer (&local_error));
          goto out;
        }
    }

  /* After we create the deploy base we must goto out on errors */
  created_deploy_base = TRUE;

  if (!flatpak_dir_deploy (self, origin, ref, NULL, (const char * const *) subpaths,
                           previous_ids, cancellable, error))
    goto out;

  if (flatpak_decomposed_is_app (ref))
    {
      g_autofree char *id = flatpak_decomposed_dup_id (ref);

      if (!flatpak_dir_make_current_ref (self, ref, cancellable, error))
        goto out;

      if (!flatpak_dir_update_exports (self, id, cancellable, error))
        goto out;
    }

  /* Remove old ref if the reinstalled was from a different remote */
  if (remove_ref_from_remote != NULL)
    {
      if (!flatpak_dir_remove_ref (self, remove_ref_from_remote, flatpak_decomposed_get_ref (ref), cancellable, error))
        goto out;

      flatpak_dir_prune_origin_remote (self, remove_ref_from_remote);
    }

  /* Release lock before doing possibly slow prune */
  glnx_release_lock_file (&lock);

  flatpak_dir_cleanup_removed (self, cancellable, NULL);

  if (!flatpak_dir_mark_changed (self, error))
    goto out;

  ret = TRUE;

  commit = flatpak_dir_read_active (self, ref, cancellable);
  flatpak_dir_log (self, "deploy install", origin, flatpak_decomposed_get_ref (ref), commit, old_active, NULL,
                   "Installed %s from %s", flatpak_decomposed_get_ref (ref), origin);

out:
  if (created_deploy_base && !ret)
    flatpak_rm_rf (deploy_base, cancellable, NULL);

  return ret;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_find_remote_refs (FlatpakDir           *self,
                              FlatpakRemoteState   *state,
                              const char           *name,
                              const char           *opt_branch,
                              const char           *opt_default_branch,
                              const char           *opt_arch,
                              const char           *opt_default_arch,
                              FlatpakKinds          kinds,
                              FindMatchingRefsFlags flags,
                              GCancellable         *cancellable,
                              GError              **error)
{
  g_autoptr(GHashTable) remote_refs = NULL;
  g_autoptr(GPtrArray) matched_refs = NULL;

  if (!flatpak_dir_list_all_remote_refs (self, state,
                                         &remote_refs, cancellable, error))
    return NULL;


  matched_refs = find_matching_refs (remote_refs,
                                     name,
                                     opt_branch,
                                     opt_default_branch,
                                     opt_arch,
                                     opt_default_arch,
                                     kinds,
                                     flags,
                                     error);
  if (matched_refs == NULL)
    return NULL;

  /* If we can't match anything and we had an error downloading (offline?), report that as its more helpful */
  if (matched_refs->len == 0 && state->summary_fetch_error)
    {
      g_propagate_error (error, g_error_copy (state->summary_fetch_error));
      return NULL;
    }

  return g_steal_pointer (&matched_refs);
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_remote_fetch_indexed_summary (FlatpakDir   *self,
                                          const char   *name_or_uri,
                                          const char   *arch,
                                          GVariant     *subsummary_info_v,
                                          gboolean      only_cached,
                                          GBytes      **out_summary,
                                          GCancellable *cancellable,
                                          GError      **error)
{
  g_autofree char *url = NULL;
  gboolean is_local;
  g_autoptr(GError) cache_error = NULL;
  g_autoptr(GBytes) summary_z = NULL;
  g_autoptr(GBytes) summary = NULL;
  g_autofree char *sha256 = NULL;
  VarSubsummaryRef subsummary_info;
  gsize checksum_bytes_len;
  const guchar *checksum_bytes;
  g_autofree char *checksum = NULL;
  g_autofree char *cache_name = NULL;

  ensure_soup_session (self);

  if (!ostree_repo_remote_get_url (self->repo, name_or_uri, &url, error))
    return FALSE;

  if (!g_str_has_prefix (name_or_uri, "file:") && flatpak_dir_get_remote_disabled (self, name_or_uri))
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
                   "Can't fetch summary from disabled remote ‘%s’", name_or_uri);
      return FALSE;
    }

  subsummary_info = var_subsummary_from_gvariant (subsummary_info_v);
  checksum_bytes = var_subsummary_peek_checksum (subsummary_info, &checksum_bytes_len);
  g_assert (checksum_bytes_len == OSTREE_SHA256_DIGEST_LEN); /* We verified this when scanning index */
  checksum = ostree_checksum_from_bytes (checksum_bytes);

  is_local = g_str_has_prefix (url, "file:");

  /* No in-memory caching for local files */
  if (!is_local)
    {
      if (flatpak_dir_lookup_cached_summary (self, out_summary, NULL, checksum, url))
        return TRUE;
    }

  cache_name = g_strconcat (name_or_uri, "-", arch, "-", checksum, NULL);

  /* First look for an on-disk cache */
  if (!flatpak_dir_remote_load_cached_summary (self, cache_name, ".sub", NULL,
                                               &summary, NULL, cancellable, &cache_error))
    {
      g_autofree char *old_checksum = NULL;
      g_autoptr(GBytes) old_summary = NULL;

      /* Else fetch it */
      if (only_cached)
        {
          g_propagate_error (error, g_steal_pointer (&cache_error));
          return FALSE;
        }

      /* Look for first applicable deltas */
      VarArrayofChecksumRef history = var_subsummary_get_history (subsummary_info);
      gsize history_len = var_arrayof_checksum_get_length (history);
      for (gsize i = 0; i < history_len; i++)
        {
          VarChecksumRef old = var_arrayof_checksum_get_at (history, i);
          g_autofree char *old_cache_name = NULL;

          if (var_checksum_get_length (old) != OSTREE_SHA256_DIGEST_LEN)
            continue;

          old_checksum = ostree_checksum_from_bytes (var_checksum_peek (old));
          old_cache_name = g_strconcat (name_or_uri, "-", arch, "-", old_checksum, NULL);
          if (flatpak_dir_remote_load_cached_summary (self, old_cache_name, ".sub", NULL,
                                                      &old_summary, NULL, cancellable, NULL))
            break;
        }

      if (old_summary)
        {
          g_autoptr(GError) delta_error = NULL;

          g_autofree char *delta_filename = g_strconcat (old_checksum, "-", checksum, ".delta", NULL);
          g_autofree char *delta_url = g_build_filename (url, "summaries", delta_filename, NULL);

          g_debug ("Fetching indexed summary delta %s for remote ‘%s’", delta_filename, name_or_uri);

          g_autoptr(GBytes) delta = flatpak_load_uri (self->soup_session, delta_url, 0, NULL,
                                                      NULL, NULL, NULL,
                                                      cancellable, &delta_error);
          if (delta == NULL)
            g_debug ("Failed to load delta, falling back: %s", delta_error->message);
          else
            {
              g_autoptr(GBytes) applied = flatpak_summary_apply_diff (old_summary, delta, &delta_error);

              if (applied == NULL)
                g_warning ("Failed to apply delta, falling back: %s", delta_error->message);
              else
                {
                  sha256 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, applied);
                  if (strcmp (sha256, checksum) != 0)
                    g_warning ("Appliying delta gave wrong checksum, falling backn");
                  else
                    summary = g_steal_pointer (&applied);
                }
            }
        }

      if (summary == NULL)
        {
          g_autofree char *filename = g_strconcat (checksum, ".gz", NULL);
          g_debug ("Fetching indexed summary file %s for remote ‘%s’", filename, name_or_uri);
          g_autofree char *subsummary_url = g_build_filename (url, "summaries", filename, NULL);
          summary_z = flatpak_load_uri (self->soup_session, subsummary_url, 0, NULL,
                                        NULL, NULL, NULL,
                                        cancellable, error);
          if (summary_z == NULL)
            return FALSE;

          summary = flatpak_zlib_decompress_bytes (summary_z, error);
          if (summary == NULL)
            return FALSE;

          sha256 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, summary);
          if (strcmp (sha256, checksum) != 0)
            return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid checksum for indexed summary %s for remote '%s'"), checksum, name_or_uri);
        }

      /* Save to disk */
      if (!is_local)
        {
          if (!flatpak_dir_remote_save_cached_summary (self, cache_name, ".sub", NULL,
                                                       summary, NULL,
                                                       cancellable, error))
            return FALSE;

          if (!flatpak_dir_gc_cached_digested_summaries (self, name_or_uri, cache_name,
                                                         cancellable, error))
            return FALSE;
        }
    }
  else
    g_debug ("Loaded indexed summary file %s from cache for remote ‘%s’", checksum, name_or_uri);

  /* Cache in memory */
  if (!is_local && !only_cached)
    flatpak_dir_cache_summary (self, summary, NULL, checksum, url);

  *out_summary = g_steal_pointer (&summary);

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_check_parental_controls (FlatpakDir    *self,
                                     const char    *ref,
                                     GBytes        *deploy_data,
                                     GCancellable  *cancellable,
                                     GError       **error)
{
#ifdef HAVE_LIBMALCONTENT
#ifdef USE_SYSTEM_HELPER
  g_autoptr(GError) local_error = NULL;
  const char *on_session = g_getenv ("FLATPAK_SYSTEM_HELPER_ON_SESSION");
  g_autoptr(GDBusConnection) dbus_connection = NULL;
  g_autoptr(MctManager) manager = NULL;
  g_autoptr(MctAppFilter) app_filter = NULL;
  const char *content_rating_type;
  g_autoptr(GHashTable) content_rating = NULL;
  g_autoptr(AutoPolkitAuthority) authority = NULL;
  g_autoptr(AutoPolkitDetails) details = NULL;
  g_autoptr(AutoPolkitSubject) subject = NULL;
  gint subject_uid;
  g_autoptr(AutoPolkitAuthorizationResult) result = NULL;
  gboolean authorized;
  gboolean repo_installation_allowed, app_is_appropriate;
  
  /* Assume that root is allowed to install any ref and shouldn't have any
   * parental controls restrictions applied to them */
  if (getuid () == 0)
    return TRUE;

  /* The ostree-metadata and appstream/ branches should not have any parental
   * controls restrictions. Similarly, for the moment, there is no point in
   * restricting runtimes. */
  if (!g_str_has_prefix (ref, "app/"))
    return TRUE;

  g_debug ("Getting parental controls details for %s from %s",
           ref, flatpak_deploy_data_get_origin (deploy_data));

  if (on_session != NULL)
    {
      /* FIXME: Instead of skipping the parental controls check in the test
       * environment, make a mock service for it.
       * https://github.com/flatpak/flatpak/issues/2993 */
      g_debug ("Skipping parental controls check for %s since the "
               "system bus is unavailable in the test environment", ref);
      return TRUE;
    }

  dbus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, &local_error);
  if (dbus_connection == NULL)
    {
      g_propagate_error (error, g_steal_pointer (&local_error));
      return FALSE;
    }

  if (self->user || self->source_pid == 0)
    subject = polkit_unix_process_new_for_owner (getpid (), 0, getuid ());
  else
    subject = polkit_unix_process_new_for_owner (self->source_pid, 0, -1);

  /* Get the parental controls for the invoking user. */
  subject_uid = polkit_unix_process_get_uid (POLKIT_UNIX_PROCESS (subject));
  if (subject_uid == -1)
    {
      g_set_error_literal (error, G_DBUS_ERROR, G_DBUS_ERROR_AUTH_FAILED,
                           "Failed to get subject UID");
      return FALSE;
    }

  manager = mct_manager_new (dbus_connection);
  app_filter = mct_manager_get_app_filter (manager, subject_uid,
                                           MCT_GET_APP_FILTER_FLAGS_INTERACTIVE,
                                           cancellable, &local_error);
  if (g_error_matches (local_error, MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_DISABLED))
    {
      g_debug ("Skipping parental controls check for %s since parental "
               "controls are disabled globally", ref);
      return TRUE;
    }
  else if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) ||
           g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
    {
      g_debug ("Skipping parental controls check for %s since a required "
               "service was not found", ref);
      return TRUE;
    }
  else if (local_error != NULL)
    {
      g_propagate_error (error, g_steal_pointer (&local_error));
      return FALSE;
    }

  /* Check the content rating against the parental controls. If the app is
   * allowed to be installed, return so immediately. */
  repo_installation_allowed = ((self->user && mct_app_filter_is_user_installation_allowed (app_filter)) ||
                               (!self->user && mct_app_filter_is_system_installation_allowed (app_filter)));

  content_rating_type = flatpak_deploy_data_get_appdata_content_rating_type (deploy_data);
  content_rating = flatpak_deploy_data_get_appdata_content_rating (deploy_data);
  app_is_appropriate = flatpak_oars_check_rating (content_rating, content_rating_type,
                                                  app_filter);

  if (repo_installation_allowed && app_is_appropriate)
    {
      g_debug ("Parental controls policy satisfied for %s", ref);
      return TRUE;
    }

  /* Otherwise, check polkit to see if the admin is going to allow the user to
   * override their parental controls policy. We can’t pass any details to this
   * polkit check, since it could be run by the user or by the system helper,
   * and non-root users can’t pass details to polkit checks. */
  authority = polkit_authority_get_sync (NULL, error);
  if (authority == NULL)
    return FALSE;

  result = polkit_authority_check_authorization_sync (authority, subject,
                                                      "org.freedesktop.Flatpak.override-parental-controls",
                                                      NULL,
                                                      POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION,
                                                      cancellable, error);
  if (result == NULL)
    return FALSE;

  authorized = polkit_authorization_result_get_is_authorized (result);

  if (!authorized)
    return flatpak_fail_error (error, FLATPAK_ERROR_PERMISSION_DENIED,
                               /* Translators: The placeholder is for an app ref. */
                               _("Installing %s is not allowed by the policy set by your administrator"),
                               ref);

  g_debug ("Parental controls policy overridden by polkit for %s", ref);
#endif  /* USE_SYSTEM_HELPER */
#endif  /* HAVE_LIBMALCONTENT */

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_install_bundle (FlatpakDir         *self,
                            GFile              *file,
                            const char         *remote,
                            FlatpakDecomposed **out_ref,
                            GCancellable       *cancellable,
                            GError            **error)
{
  g_autofree char *ref_str = NULL;
  g_autoptr(FlatpakDecomposed) ref = NULL;
  g_autoptr(GBytes) deploy_data = NULL;
  g_autoptr(GVariant) metadata = NULL;
  g_autofree char *origin = NULL;
  g_autofree char *to_checksum = NULL;
  gboolean gpg_verify;

  if (!flatpak_dir_check_add_remotes_config_dir (self, error))
    return FALSE;

  if (flatpak_dir_use_system_helper (self, NULL))
    {
      const char *installation = flatpak_dir_get_id (self);

      if (!flatpak_dir_system_helper_call_install_bundle (self,
                                                          flatpak_file_get_path_cached (file),
                                                          0, remote,
                                                          installation ? installation : "",
                                                          &ref_str,
                                                          cancellable,
                                                          error))
        return FALSE;


      ref = flatpak_decomposed_new_from_ref (ref_str, error);
      if (ref == NULL)
        return FALSE;

      if (out_ref)
        *out_ref = g_steal_pointer (&ref);

      return TRUE;
    }

  if (!flatpak_dir_ensure_repo (self, cancellable, error))
    return FALSE;

  metadata = flatpak_bundle_load (file, &to_checksum,
                                  &ref,
                                  &origin,
                                  NULL, NULL,
                                  NULL, NULL, NULL,
                                  error);
  if (metadata == NULL)
    return FALSE;

  deploy_data = flatpak_dir_get_deploy_data (self, ref, FLATPAK_DEPLOY_VERSION_ANY, cancellable, NULL);
  if (deploy_data != NULL)
    {
      if (strcmp (flatpak_deploy_data_get_commit (deploy_data), to_checksum) == 0)
        {
          g_autofree char *id = flatpak_decomposed_dup_id (ref);
          g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED,
                       _("This version of %s is already installed"), id);
          return FALSE;
        }

      if (strcmp (remote, flatpak_deploy_data_get_origin (deploy_data)) != 0)
        {
          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                       _("Can't change remote during bundle install"));
          return FALSE;
        }
    }

  if (!ostree_repo_remote_get_gpg_verify (self->repo, remote,
                                          &gpg_verify, error))
    return FALSE;

  if (!flatpak_pull_from_bundle (self->repo,
                                 file,
                                 remote,
                                 flatpak_decomposed_get_ref (ref),
                                 gpg_verify,
                                 cancellable,
                                 error))
    return FALSE;

  if (deploy_data != NULL)
    {
      g_autofree char *group = g_strdup_printf ("remote \"%s\"", remote);
      g_autofree char *old_url = NULL;
      g_autoptr(GKeyFile) new_config = NULL;

      /* The pull succeeded, and this is an update. So, we need to update the repo config
         if anything changed */
      ostree_repo_remote_get_url (self->repo,
                                  remote,
                                  &old_url,
                                  NULL);
      if (origin != NULL &&
          (old_url == NULL || strcmp (old_url, origin) != 0))
        {
          if (new_config == NULL)
            new_config = ostree_repo_copy_config (self->repo);

          g_key_file_set_value (new_config, group, "url", origin);
        }

      if (new_config)
        {
          if (!flatpak_dir_cleanup_remote_for_url_change (self, remote,
                                                          origin, cancellable, error))
            return FALSE;

          if (!ostree_repo_write_config (self->repo, new_config, error))
            return FALSE;
        }
    }

  if (deploy_data)
    {
      if (!flatpak_dir_deploy_update (self, ref, NULL, NULL, NULL, cancellable, error))
        return FALSE;
    }
  else
    {
      if (!flatpak_dir_deploy_install (self, ref, remote, NULL, NULL, FALSE, cancellable, error))
        return FALSE;
    }

  if (out_ref)
    *out_ref = g_steal_pointer (&ref);

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_remote_make_oci_summary (FlatpakDir   *self,
                                     const char   *remote,
                                     gboolean      only_cached,
                                     GBytes      **out_summary,
                                     GCancellable *cancellable,
                                     GError      **error)
{
  g_autoptr(GVariant) summary = NULL;
  g_autoptr(GFile) index_cache = NULL;
  g_autofree char *index_uri = NULL;
  g_autoptr(GFile) summary_cache = NULL;
  g_autofree char *self_name = NULL;
  g_autoptr(GError) local_error = NULL;
  g_autoptr(GMappedFile) mfile = NULL;
  g_autoptr(GBytes) cache_bytes = NULL;
  g_autoptr(GBytes) summary_bytes = NULL;

  if (flatpak_dir_use_system_helper (self, NULL))
    {
      const char *installation = flatpak_dir_get_id (self);
      FlatpakHelperGenerateOciSummaryFlags flags = FLATPAK_HELPER_GENERATE_OCI_SUMMARY_FLAGS_NONE;

      if (only_cached)
        flags |= FLATPAK_HELPER_GENERATE_OCI_SUMMARY_FLAGS_ONLY_CACHED;

      if (!flatpak_dir_system_helper_call_generate_oci_summary (self,
                                                                flags,
                                                                remote,
                                                                installation ? installation : "",
                                                                cancellable, error))
        return FALSE;

      summary_cache = flatpak_dir_get_oci_summary_location (self, remote, error);
      if (summary_cache == NULL)
        return FALSE;
    }
  else
    {
      self_name = flatpak_dir_get_name (self);

      index_cache = flatpak_dir_update_oci_index (self, remote, &index_uri, cancellable, error);
      if (index_cache == NULL)
        return FALSE;

      summary_cache = flatpak_dir_get_oci_summary_location (self, remote, error);
      if (summary_cache == NULL)
        return FALSE;

      if (!only_cached && !check_destination_mtime (index_cache, summary_cache, cancellable))
        {
          summary = flatpak_oci_index_make_summary (index_cache, index_uri, cancellable, &local_error);
          if (summary == NULL)
            {
              g_propagate_error (error, g_steal_pointer (&local_error));
              return FALSE;
            }

          summary_bytes = g_variant_get_data_as_bytes (summary);

          if (!g_file_replace_contents (summary_cache,
                                        g_bytes_get_data (summary_bytes, NULL),
                                        g_bytes_get_size (summary_bytes),
                                        NULL, FALSE, 0, NULL, cancellable, error))
            {
              g_prefix_error (error, _("Failed to write summary cache: "));
              return FALSE;
            }

          if (out_summary)
            *out_summary = g_steal_pointer (&summary_bytes);
          return TRUE;
        }
    }

  if (out_summary)
    {
      mfile = g_mapped_file_new (flatpak_file_get_path_cached (summary_cache), FALSE, error);
      if (mfile == NULL)
        {
          if (only_cached)
            {
              g_clear_error (error);
              g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_CACHED,
                           _("No oci summary cached for remote '%s'"), remote);
            }

          return FALSE;
        }

      cache_bytes = g_mapped_file_get_bytes (mfile);
      *out_summary = g_steal_pointer (&cache_bytes);
    }

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_pull_oci (FlatpakDir          *self,
                      FlatpakRemoteState  *state,
                      const char          *ref,
                      const char          *opt_rev,
                      OstreeRepo          *repo,
                      FlatpakPullFlags     flatpak_flags,
                      OstreeRepoPullFlags  flags,
                      const char          *token,
                      FlatpakProgress     *progress,
                      GCancellable        *cancellable,
                      GError             **error)
{
  g_autoptr(FlatpakOciRegistry) registry = NULL;
  g_autoptr(FlatpakOciVersioned) versioned = NULL;
  g_autoptr(FlatpakOciImage) image_config = NULL;
  g_autofree char *full_ref = NULL;
  const char *oci_repository = NULL;
  const char *delta_url = NULL;
  g_autofree char *oci_digest = NULL;
  g_autofree char *checksum = NULL;
  VarRefInfoRef latest_rev_info;
  g_autofree char *latest_alt_commit = NULL;
  VarMetadataRef metadata;
  g_autofree char *latest_rev = NULL;
  G_GNUC_UNUSED g_autofree char *latest_commit =
    flatpak_dir_read_latest (self, state->remote_name, ref, &latest_alt_commit, cancellable, NULL);
  g_autofree char *name = NULL;

  /* We use the summary so that we can reuse any cached json */
  if (!flatpak_remote_state_lookup_ref (state, ref, &latest_rev, NULL, &latest_rev_info, NULL, error))
    return FALSE;
  if (latest_rev == NULL)
    return flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND,
                               _("Couldn't find latest checksum for ref %s in remote %s"),
                               ref, state->remote_name);

  metadata = var_ref_info_get_metadata (latest_rev_info);
  oci_repository = var_metadata_lookup_string (metadata, "xa.oci-repository", NULL);
  delta_url = var_metadata_lookup_string (metadata, "xa.delta-url", NULL);

  oci_digest = g_strconcat ("sha256:", opt_rev != NULL ? opt_rev : latest_rev, NULL);

  /* Short circuit if we've already got this commit */
  if (latest_alt_commit != NULL && strcmp (oci_digest + strlen ("sha256:"), latest_alt_commit) == 0)
    return TRUE;

  registry = flatpak_remote_state_new_oci_registry (state, token, cancellable, error);
  if (registry == NULL)
    return FALSE;

  versioned = flatpak_oci_registry_load_versioned (registry, oci_repository, oci_digest,
                                                   NULL, NULL, cancellable, error);
  if (versioned == NULL)
    return FALSE;

  if (!FLATPAK_IS_OCI_MANIFEST (versioned))
    return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Image is not a manifest"));

  image_config = flatpak_oci_registry_load_image_config (registry, oci_repository,
                                                         FLATPAK_OCI_MANIFEST (versioned)->config.digest,
                                                         (const char **)FLATPAK_OCI_MANIFEST (versioned)->config.urls,
                                                         NULL, cancellable, error);
  if (image_config == NULL)
    return FALSE;

  full_ref = g_strdup_printf ("%s:%s", state->remote_name, ref);

  if (repo == NULL)
    repo = self->repo;

  flatpak_progress_start_oci_pull (progress);

  g_debug ("Pulling OCI image %s", oci_digest);

  checksum = flatpak_pull_from_oci (repo, registry, oci_repository, oci_digest, delta_url, FLATPAK_OCI_MANIFEST (versioned), image_config,
                                    state->remote_name, ref, flatpak_flags, oci_pull_progress_cb, progress, cancellable, error);

  if (checksum == NULL)
    return FALSE;

  g_debug ("Imported OCI image as checksum %s", checksum);

  if (repo == self->repo)
    name = flatpak_dir_get_name (self);
  else
    {
      GFile *file = ostree_repo_get_path (repo);
      name = g_file_get_path (file);
    }

  (flatpak_dir_log) (self, __FILE__, __LINE__, __FUNCTION__, name,
                     "pull oci", flatpak_oci_registry_get_uri (registry), ref, NULL, NULL, NULL,
                     "Pulled %s from %s", ref, flatpak_oci_registry_get_uri (registry));

  return TRUE;
}

Project: flatpak
File: flatpak-dir.c

_flatpak_dir_get_remote_state (FlatpakDir   *self,
                               const char   *remote_or_uri,
                               gboolean      optional,
                               gboolean      local_only,
                               gboolean      only_cached,
                               gboolean      opt_summary_is_index,
                               GBytes       *opt_summary,
                               GBytes       *opt_summary_sig,
                               GCancellable *cancellable,
                               GError      **error)
{
  g_autoptr(FlatpakRemoteState) state = flatpak_remote_state_new ();
  g_autoptr(GPtrArray) sideload_paths = NULL;
  g_autofree char *url = NULL;
  g_autoptr(GError) my_error = NULL;
  gboolean is_local;
  gboolean got_summary = FALSE;
  const char *arch = flatpak_get_default_arch ();
  g_autoptr(GBytes) index_bytes = NULL;
  g_autoptr(GBytes) index_sig_bytes = NULL;
  g_autoptr(GBytes) summary_bytes = NULL;
  g_autoptr(GBytes) summary_sig_bytes = NULL;

  if (error == NULL)
    error = &my_error;

  if (!flatpak_dir_ensure_repo (self, cancellable, error))
    return NULL;

  state->remote_name = g_strdup (remote_or_uri);
  state->is_file_uri = is_local = g_str_has_prefix (remote_or_uri, "file:");
  if (!is_local)
    {
      if (!flatpak_dir_has_remote (self, remote_or_uri, error))
        return NULL;
      if (!repo_get_remote_collection_id (self->repo, remote_or_uri, &state->collection_id, error))
        return NULL;
      if (!flatpak_dir_lookup_remote_filter (self, remote_or_uri, FALSE, NULL, &state->allow_refs, &state->deny_refs, error))
        return NULL;
      if (!ostree_repo_remote_get_url (self->repo, remote_or_uri, &url, error))
        return FALSE;

      state->default_token_type = flatpak_dir_get_remote_default_token_type (self, remote_or_uri);
    }

  sideload_paths = flatpak_dir_get_sideload_repo_paths (self);
  for (int i = 0; i < sideload_paths->len; i++)
    flatpak_remote_state_add_sideload_repo (state, g_ptr_array_index (sideload_paths, i));

  if (local_only)
    {
      flatpak_fail (&state->summary_fetch_error, "Internal error, local_only state");
      return g_steal_pointer (&state);
    }

  if (opt_summary)
    {
      if (opt_summary_sig)
        {
          /* If specified, must be valid signature */
          g_autoptr(OstreeGpgVerifyResult) gpg_result =
            ostree_repo_verify_summary (self->repo,
                                        state->remote_name,
                                        opt_summary,
                                        opt_summary_sig,
                                        NULL, error);
          if (gpg_result == NULL ||
              !ostree_gpg_verify_result_require_valid_signature (gpg_result, error))
            return NULL;
        }

      if (opt_summary_is_index)
        {
          if (opt_summary_sig)
            index_sig_bytes = g_bytes_ref (opt_summary_sig);
          index_bytes = g_bytes_ref (opt_summary);
        }
      else
        {
          if (opt_summary_sig)
            summary_sig_bytes = g_bytes_ref (opt_summary_sig);
          summary_bytes = g_bytes_ref (opt_summary);
        }

      got_summary = TRUE;
    }

  /* First try the memory cache. Note: No in-memory caching for local files. */
  if (!is_local)
    {
      if (!got_summary)
        {
          g_autofree char *index_cache_key = g_strconcat ("index-", remote_or_uri, NULL);
          if (flatpak_dir_lookup_cached_summary (self, &index_bytes, &index_sig_bytes, index_cache_key, url))
            got_summary = TRUE;
        }

      if (!got_summary)
        {
          g_autofree char *summary_cache_key = g_strconcat ("summary-", remote_or_uri, NULL);
          if (flatpak_dir_lookup_cached_summary (self, &summary_bytes, &summary_sig_bytes, summary_cache_key, url))
            got_summary = TRUE;
        }
    }

  /* Then look for an indexed summary on disk/network */
  if (!got_summary)
    {
      g_autoptr(GError) local_error = NULL;

      if (flatpak_dir_remote_fetch_summary_index (self, remote_or_uri, only_cached, &index_bytes, &index_sig_bytes,
                                                  cancellable, &local_error))
        {
          got_summary = TRUE;
        }
      else
        {
          if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
              !g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_CACHED))
            {
              /* We got an error other than not-found, assume we're indexed but there is some network error */
              got_summary = TRUE;
              if (optional && !g_cancellable_is_cancelled (cancellable))
                {
                  g_debug ("Failed to download optional summary index: %s", local_error->message);
                  state->summary_fetch_error = g_steal_pointer (&local_error);
                }
              else
                {
                  g_propagate_error (error, g_steal_pointer (&local_error));
                  return NULL;
                }
            }
        }
    }

  if (!got_summary)
    {
      /* No index, fall back to full summary */
      g_autoptr(GError) local_error = NULL;

      if (flatpak_dir_remote_fetch_summary (self, remote_or_uri, only_cached, &summary_bytes, &summary_sig_bytes,
                                            cancellable, &local_error))
        {
          got_summary = TRUE;
        }
      else
        {
          if (optional && !g_cancellable_is_cancelled (cancellable))
            {
              g_debug ("Failed to download optional summary: %s", local_error->message);
              state->summary_fetch_error = g_steal_pointer (&local_error);
            }
          else
            {
              g_propagate_error (error, g_steal_pointer (&local_error));
              return NULL;
            }
        }
    }

  if (index_bytes)
    {
      state->index = g_variant_ref_sink (g_variant_new_from_bytes (FLATPAK_SUMMARY_INDEX_GVARIANT_FORMAT,
                                                                   index_bytes, FALSE));
      state->index_sig_bytes = g_steal_pointer (&index_sig_bytes);
    }
  else if (summary_bytes)
    {
      state->summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT,
                                                                     summary_bytes, FALSE));
      state->summary_bytes = g_steal_pointer (&summary_bytes);
      state->summary_sig_bytes = g_steal_pointer (&summary_sig_bytes);
    }

  if (state->index)
    {
      g_autofree char *require_subset = flatpak_dir_get_remote_subset (self, state->remote_name);
      VarSummaryIndexRef index = var_summary_index_from_gvariant (state->index);
      VarSummaryIndexSubsummariesRef subsummaries = var_summary_index_get_subsummaries (index);
      gsize n_subsummaries = var_summary_index_subsummaries_get_length (subsummaries);

      state->index_ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);

      for (gsize i = 0; i < n_subsummaries; i++)
        {
          VarSummaryIndexSubsummariesEntryRef entry = var_summary_index_subsummaries_get_at (subsummaries, i);
          const char *name = var_summary_index_subsummaries_entry_get_key (entry);
          VarSubsummaryRef subsummary = var_summary_index_subsummaries_entry_get_value (entry);
          gsize checksum_bytes_len;
          const guchar *checksum_bytes;
          g_autofree char *digest = NULL;
          const char *dash, *subsummary_arch;

          dash = strchr (name, '-');
          subsummary_arch = dash == NULL ? name : dash + 1;

          if (dash == NULL) /* No subset */
            {
              if (require_subset != NULL)
                continue;
            }
          else /* Subset */
            {
              if (require_subset == NULL)
                continue;
              else
                {
                  g_autofree char *subset = g_strndup (name, dash - name);
                  if (strcmp (require_subset, subset) != 0)
                    continue;
                }
            }

          checksum_bytes = var_subsummary_peek_checksum (subsummary, &checksum_bytes_len);
          if (G_UNLIKELY (checksum_bytes_len != OSTREE_SHA256_DIGEST_LEN))
            {
              g_debug ("Invalid checksum for digested summary, not using cache");
              continue;
            }
          digest = ostree_checksum_from_bytes (checksum_bytes);

          g_hash_table_insert (state->index_ht, g_strdup (subsummary_arch), var_subsummary_to_owned_gvariant (subsummary, state->index));
        }

      /* Always load default (or specified) arch subsummary. Further arches can be manually loaded with flatpak_remote_state_ensure_subsummary. */
      if (opt_summary == NULL &&
          !flatpak_remote_state_ensure_subsummary (state, self, arch, only_cached, cancellable, error))
        return NULL;
    }

  if (state->collection_id != NULL &&
      state->summary != NULL &&
      !_validate_summary_for_collection_id (state->summary, state->collection_id, error))
    return NULL;

  if (flatpak_dir_get_remote_oci (self, remote_or_uri))
    {
      state->default_token_type = 1;
    }

  if (state->summary != NULL || state->index != NULL) /* In the optional case we might not have a summary */
    {
      VarMetadataRef meta = flatpak_remote_state_get_main_metadata (state);
      VarVariantRef res;

      if (var_metadata_lookup (meta, "xa.default-token-type", NULL, &res) &&
          var_variant_is_type (res, G_VARIANT_TYPE_INT32))
        state->default_token_type = GINT32_FROM_LE (var_variant_get_int32 (res));
    }

  return g_steal_pointer (&state);
}

Project: flatpak
File: flatpak-dir.c

flatpak_dir_remote_load_cached_summary (FlatpakDir   *self,
                                        const char   *basename,
                                        const char   *main_ext,
                                        const char   *sig_ext,
                                        GBytes      **out_main,
                                        GBytes      **out_sig,
                                        GCancellable *cancellable,
                                        GError      **error)
{
  g_autofree char *main_file_name = g_strconcat (basename, main_ext, NULL);
  g_autofree char *sig_file_name = g_strconcat (basename, sig_ext, NULL);
  g_autoptr(GFile) main_cache_file = flatpak_build_file (self->cache_dir, "summaries", main_file_name, NULL);
  g_autoptr(GFile) sig_cache_file = flatpak_build_file (self->cache_dir, "summaries", sig_file_name, NULL);
  g_autoptr(GMappedFile) mfile = NULL;
  g_autoptr(GMappedFile) sig_mfile = NULL;

  mfile = g_mapped_file_new (flatpak_file_get_path_cached (main_cache_file), FALSE, NULL);
  if (mfile == NULL)
    {
      g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_CACHED,
                   _("No cached summary for remote '%s'"), basename);
      return FALSE;
    }

  if (out_sig)
    sig_mfile = g_mapped_file_new (flatpak_file_get_path_cached (sig_cache_file), FALSE, NULL);

  *out_main = g_mapped_file_get_bytes (mfile);
  if (sig_mfile)
    *out_sig = g_mapped_file_get_bytes (sig_mfile);

  return TRUE;
}