diff options
Diffstat (limited to 'common')
38 files changed, 4492 insertions, 1175 deletions
diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc index 09e4bb68..aa4dc6e2 100644 --- a/common/Makefile.am.inc +++ b/common/Makefile.am.inc @@ -36,6 +36,7 @@ common/flatpak-enum-types.c: $(flatpakinclude_HEADERS) common/flatpak-enum-types common/flatpak-enum-types.c.tmp && mv common/flatpak-enum-types.c.tmp common/flatpak-enum-types.c EXTRA_DIST += common/flatpak-enum-types.c.template common/flatpak-enum-types.h.template +EXTRA_DIST += common/meson.build common/flatpak-dbus-generated.c: data/org.freedesktop.Flatpak.xml data/org.freedesktop.Flatpak.Authenticator.xml Makefile mkdir -p $(builddir)/common @@ -168,6 +169,8 @@ libflatpak_common_la_SOURCES = \ common/flatpak-utils-http.c \ common/flatpak-utils-private.h \ common/flatpak-utils.c \ + common/flatpak-uri-private.h \ + common/flatpak-uri.c \ common/flatpak-prune.c \ common/flatpak-prune-private.h \ common/flatpak-zstd-decompressor.c \ @@ -197,6 +200,7 @@ libflatpak_common_la_CFLAGS = \ $(MALCONTENT_CFLAGS) \ $(OSTREE_CFLAGS) \ $(POLKIT_CFLAGS) \ + $(CURL_CFLAGS) \ $(SOUP_CFLAGS) \ $(SYSTEMD_CFLAGS) \ $(XAUTH_CFLAGS) \ @@ -214,6 +218,7 @@ libflatpak_common_la_LIBADD = \ $(MALCONTENT_LIBS) \ $(OSTREE_LIBS) \ $(POLKIT_LIBS) \ + $(CURL_LIBS) \ $(SOUP_LIBS) \ $(SYSTEMD_LIBS) \ $(XAUTH_LIBS) \ @@ -233,6 +238,7 @@ libflatpak_la_CFLAGS = \ $(AM_CFLAGS) \ $(BASE_CFLAGS) \ $(OSTREE_CFLAGS) \ + $(CURL_CFLAGS) \ $(SOUP_CFLAGS) \ $(JSON_CFLAGS) \ $(NULL) @@ -251,6 +257,7 @@ libflatpak_la_LIBADD = \ libglnx.la \ $(BASE_LIBS) \ $(OSTREE_LIBS) \ + $(CURL_LIBS) \ $(SOUP_LIBS) \ $(JSON_LIBS) \ $(NULL) diff --git a/common/flatpak-appdata.c b/common/flatpak-appdata.c index 393776fa..5a14b1a0 100644 --- a/common/flatpak-appdata.c +++ b/common/flatpak-appdata.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or diff --git a/common/flatpak-auth.c b/common/flatpak-auth.c index 9e45da41..aac2a115 100644 --- a/common/flatpak-auth.c +++ b/common/flatpak-auth.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or diff --git a/common/flatpak-bundle-ref.c b/common/flatpak-bundle-ref.c index 7ce4429c..db97bc04 100644 --- a/common/flatpak-bundle-ref.c +++ b/common/flatpak-bundle-ref.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2015 Red Hat, Inc * * This program is free software; you can redistribute it and/or diff --git a/common/flatpak-bwrap.c b/common/flatpak-bwrap.c index 4367201b..6949c0be 100644 --- a/common/flatpak-bwrap.c +++ b/common/flatpak-bwrap.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2014-2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -372,7 +372,7 @@ flatpak_bwrap_bundle_args (FlatpakBwrap *bwrap, fd = glnx_steal_fd (&args_tmpf.fd); - flatpak_debug2 ("bwrap --args %d = ...", fd); + g_debug ("bwrap --args %d = ...", fd); for (i = start; i < end; i++) { @@ -380,11 +380,11 @@ flatpak_bwrap_bundle_args (FlatpakBwrap *bwrap, { g_autofree char *quoted = g_shell_quote (bwrap->argv->pdata[i]); - flatpak_debug2 (" %s", quoted); + g_debug (" %s", quoted); } else { - flatpak_debug2 (" %s", (const char *) bwrap->argv->pdata[i]); + g_debug (" %s", (const char *) bwrap->argv->pdata[i]); } } diff --git a/common/flatpak-context-private.h b/common/flatpak-context-private.h index 099b6275..d9a79777 100644 --- a/common/flatpak-context-private.h +++ b/common/flatpak-context-private.h @@ -49,6 +49,7 @@ typedef enum { FLATPAK_CONTEXT_SOCKET_SSH_AUTH = 1 << 6, FLATPAK_CONTEXT_SOCKET_PCSC = 1 << 7, FLATPAK_CONTEXT_SOCKET_CUPS = 1 << 8, + FLATPAK_CONTEXT_SOCKET_GPG_AGENT = 1 << 9, } FlatpakContextSockets; typedef enum { diff --git a/common/flatpak-context.c b/common/flatpak-context.c index dfec7ffa..9e028fb5 100644 --- a/common/flatpak-context.c +++ b/common/flatpak-context.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2014-2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -61,6 +61,7 @@ const char *flatpak_context_sockets[] = { "ssh-auth", "pcsc", "cups", + "gpg-agent", NULL }; @@ -1612,7 +1613,7 @@ flatpak_context_load_metadata (FlatpakContext *context, share = flatpak_context_share_from_string (parse_negated (shares[i], &remove), NULL); if (share == 0) - g_debug ("Unknown share type %s", shares[i]); + g_info ("Unknown share type %s", shares[i]); else { if (remove) @@ -1634,7 +1635,7 @@ flatpak_context_load_metadata (FlatpakContext *context, { FlatpakContextSockets socket = flatpak_context_socket_from_string (parse_negated (sockets[i], &remove), NULL); if (socket == 0) - g_debug ("Unknown socket type %s", sockets[i]); + g_info ("Unknown socket type %s", sockets[i]); else { if (remove) @@ -1657,7 +1658,7 @@ flatpak_context_load_metadata (FlatpakContext *context, { FlatpakContextDevices device = flatpak_context_device_from_string (parse_negated (devices[i], &remove), NULL); if (device == 0) - g_debug ("Unknown device type %s", devices[i]); + g_info ("Unknown device type %s", devices[i]); else { if (remove) @@ -1680,7 +1681,7 @@ flatpak_context_load_metadata (FlatpakContext *context, { FlatpakContextFeatures feature = flatpak_context_feature_from_string (parse_negated (features[i], &remove), NULL); if (feature == 0) - g_debug ("Unknown feature type %s", features[i]); + g_info ("Unknown feature type %s", features[i]); else { if (remove) @@ -1706,7 +1707,7 @@ flatpak_context_load_metadata (FlatpakContext *context, if (!flatpak_context_parse_filesystem (fs, remove, &filesystem, &mode, NULL)) - g_debug ("Unknown filesystem type %s", filesystems[i]); + g_info ("Unknown filesystem type %s", filesystems[i]); else { g_assert (mode == FLATPAK_FILESYSTEM_MODE_NONE || !remove); @@ -2361,7 +2362,10 @@ flatpak_context_add_bus_filters (FlatpakContext *context, flatpak_bwrap_add_arg_printf (bwrap, "--own=org.mpris.MediaPlayer2.%s.*", app_id); } else - flatpak_bwrap_add_arg_printf (bwrap, "--own=%s.Sandboxed.*", app_id); + { + flatpak_bwrap_add_arg_printf (bwrap, "--own=%s.Sandboxed.*", app_id); + flatpak_bwrap_add_arg_printf (bwrap, "--own=org.mpris.MediaPlayer2.%s.Sandboxed.*", app_id); + } } if (session_bus) @@ -2431,8 +2435,8 @@ flatpak_context_make_sandboxed (FlatpakContext *context) } const char *dont_mount_in_root[] = { - ".", "..", "lib", "lib32", "lib64", "bin", "sbin", "usr", "boot", "root", - "tmp", "etc", "app", "run", "proc", "sys", "dev", "var", NULL + ".", "..", "lib", "lib32", "lib64", "bin", "sbin", "usr", "boot", "efi", + "root", "tmp", "etc", "app", "run", "proc", "sys", "dev", "var", NULL }; static void @@ -2459,7 +2463,7 @@ flatpak_context_export (FlatpakContext *context, DIR *dir; struct dirent *dirent; - g_debug ("Allowing host-fs access"); + g_info ("Allowing host-fs access"); home_access = TRUE; /* Bind mount most dirs in / into the new root */ @@ -2496,7 +2500,7 @@ flatpak_context_export (FlatpakContext *context, home_mode = GPOINTER_TO_INT (g_hash_table_lookup (context->filesystems, "home")); if (home_mode != FLATPAK_FILESYSTEM_MODE_NONE) { - g_debug ("Allowing homedir access"); + g_info ("Allowing homedir access"); home_access = TRUE; flatpak_exports_add_path_expose (exports, MAX (home_mode, fs_mode), g_get_home_dir ()); @@ -2531,14 +2535,17 @@ flatpak_context_export (FlatpakContext *context, /* xdg-user-dirs sets disabled dirs to $HOME, and its in general not a good idea to set full access to $HOME other than explicitly, so we ignore these */ - g_debug ("Xdg dir %s is $HOME (i.e. disabled), ignoring", filesystem); + g_info ("Xdg dir %s is $HOME (i.e. disabled), ignoring", filesystem); continue; } subpath = g_build_filename (path, rest, NULL); if (mode == FLATPAK_FILESYSTEM_MODE_CREATE && do_create) - g_mkdir_with_parents (subpath, 0755); + { + if (g_mkdir_with_parents (subpath, 0755) != 0) + g_info ("Unable to create directory %s", subpath); + } if (g_file_test (subpath, G_FILE_TEST_EXISTS)) { @@ -2556,7 +2563,10 @@ flatpak_context_export (FlatpakContext *context, path = g_build_filename (g_get_home_dir (), filesystem + 2, NULL); if (mode == FLATPAK_FILESYSTEM_MODE_CREATE && do_create) - g_mkdir_with_parents (path, 0755); + { + if (g_mkdir_with_parents (path, 0755) != 0) + g_info ("Unable to create directory %s", path); + } if (g_file_test (path, G_FILE_TEST_EXISTS)) flatpak_exports_add_path_expose_or_hide (exports, mode, path); @@ -2564,7 +2574,10 @@ flatpak_context_export (FlatpakContext *context, else if (g_str_has_prefix (filesystem, "/")) { if (mode == FLATPAK_FILESYSTEM_MODE_CREATE && do_create) - g_mkdir_with_parents (filesystem, 0755); + { + if (g_mkdir_with_parents (filesystem, 0755) != 0) + g_info ("Unable to create directory %s", filesystem); + } if (g_file_test (filesystem, G_FILE_TEST_EXISTS)) flatpak_exports_add_path_expose_or_hide (exports, mode, filesystem); @@ -2693,7 +2706,8 @@ flatpak_context_append_bwrap_filesystem (FlatpakContext *context, g_autofree char *src = g_build_filename (g_get_home_dir (), ".var/app", app_id, persist, NULL); g_autofree char *dest = g_build_filename (g_get_home_dir (), persist, NULL); - g_mkdir_with_parents (src, 0755); + if (g_mkdir_with_parents (src, 0755) != 0) + g_info ("Unable to create directory %s", src); flatpak_bwrap_add_bind_arg (bwrap, "--bind", src, dest); } diff --git a/common/flatpak-dir-private.h b/common/flatpak-dir-private.h index 74d2e974..950944b3 100644 --- a/common/flatpak-dir-private.h +++ b/common/flatpak-dir-private.h @@ -70,6 +70,7 @@ GType flatpak_deploy_get_type (void); #define FLATPAK_REF_BRANCH_KEY "Branch" #define FLATPAK_REF_COLLECTION_ID_KEY "CollectionID" #define FLATPAK_REF_DEPLOY_COLLECTION_ID_KEY "DeployCollectionID" +#define FLATPAK_REF_DEPLOY_SIDELOAD_COLLECTION_ID_KEY "DeploySideloadCollectionID" #define FLATPAK_REPO_GROUP "Flatpak Repo" #define FLATPAK_REPO_VERSION_KEY "Version" @@ -89,6 +90,7 @@ GType flatpak_deploy_get_type (void); #define FLATPAK_REPO_COLLECTION_ID_KEY "CollectionID" #define FLATPAK_REPO_DEPLOY_COLLECTION_ID_KEY "DeployCollectionID" +#define FLATPAK_REPO_DEPLOY_SIDELOAD_COLLECTION_ID_KEY "DeploySideloadCollectionID" #define FLATPAK_CLI_UPDATE_INTERVAL_MS 300 @@ -646,10 +648,19 @@ GPtrArray * flatpak_dir_list_refs (Fla FlatpakKinds kinds, GCancellable *cancellable, GError **error); +gboolean flatpak_dir_is_runtime_extension (FlatpakDir *self, + FlatpakDecomposed *ref); GPtrArray * flatpak_dir_list_app_refs_with_runtime (FlatpakDir *self, + GHashTable **runtime_app_map, FlatpakDecomposed *runtime_ref, GCancellable *cancellable, GError **error); +GPtrArray * flatpak_dir_list_app_refs_with_runtime_extension (FlatpakDir *self, + GHashTable **runtime_app_map, + GHashTable **extension_app_map, + FlatpakDecomposed *runtime_ext_ref, + GCancellable *cancellable, + GError **error); GVariant * flatpak_dir_read_latest_commit (FlatpakDir *self, const char *remote, FlatpakDecomposed *ref, diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c index 7471f529..927f26b6 100644 --- a/common/flatpak-dir.c +++ b/common/flatpak-dir.c @@ -1,4 +1,5 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: + * * Copyright © 2014-2019 Red Hat, Inc * Copyright © 2017 Endless Mobile, Inc. * @@ -165,7 +166,7 @@ static gboolean flatpak_dir_lookup_remote_filter (FlatpakDir *self, GRegex **deny_regex, GError **error); -static void ensure_soup_session (FlatpakDir *self); +static void ensure_http_session (FlatpakDir *self); static void flatpak_dir_log (FlatpakDir *self, const char *file, @@ -241,7 +242,7 @@ struct FlatpakDir GRegex *masked; GRegex *pinned; - SoupSession *soup_session; + FlatpakHttpSession *http_session; }; G_LOCK_DEFINE_STATIC (config_cache); @@ -430,14 +431,14 @@ flatpak_remote_state_add_sideload_repo (FlatpakRemoteState *self, /* We expect to hit this code path when the repo is providing things * from other remotes */ - g_debug ("Sideload repo at path %s not valid for remote %s: %s", - flatpak_file_get_path_cached (dir), self->remote_name, local_error->message); + g_info ("Sideload repo at path %s not valid for remote %s: %s", + flatpak_file_get_path_cached (dir), self->remote_name, local_error->message); flatpak_sideload_state_free (ss); } else { g_ptr_array_add (self->sideload_repos, ss); - g_debug ("Using sideloaded repo %s for remote %s", flatpak_file_get_path_cached (dir), self->remote_name); + g_info ("Using sideloaded repo %s for remote %s", flatpak_file_get_path_cached (dir), self->remote_name); } } } @@ -808,7 +809,7 @@ flatpak_remote_state_match_subrefs (FlatpakRemoteState *self, if (self->summary == NULL && self->index == NULL) { - g_debug ("flatpak_remote_state_match_subrefs with no summary"); + g_info ("flatpak_remote_state_match_subrefs with no summary"); return g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref); } @@ -1160,14 +1161,14 @@ flatpak_remote_state_fetch_commit_object (FlatpakRemoteState *self, if (!ostree_repo_remote_get_url (dir->repo, self->remote_name, &base_url, error)) return NULL; - ensure_soup_session (dir); + ensure_http_session (dir); part1 = g_strndup (checksum, 2); part2 = g_strdup_printf ("%s.commit", checksum + 2); object_url = g_build_filename (base_url, "objects", part1, part2, NULL); - bytes = flatpak_load_uri (dir->soup_session, object_url, 0, token, + bytes = flatpak_load_uri (dir->http_session, object_url, 0, token, NULL, NULL, NULL, cancellable, error); if (bytes == NULL) @@ -1643,7 +1644,7 @@ append_locations_from_config_file (GPtrArray *locations, if (!g_key_file_load_from_file (keyfile, file_path, G_KEY_FILE_NONE, &my_error)) { - g_debug ("Could not get list of system installations from '%s': %s", file_path, my_error->message); + g_info ("Could not get list of system installations from '%s': %s", file_path, my_error->message); g_propagate_error (error, g_steal_pointer (&my_error)); goto out; } @@ -1689,7 +1690,7 @@ append_locations_from_config_file (GPtrArray *locations, path = g_key_file_get_string (keyfile, groups[i], "Path", &my_error); if (path == NULL) { - g_debug ("While reading '%s': Unable to get path for installation '%s': %s", file_path, id, my_error->message); + g_info ("While reading '%s': Unable to get path for installation '%s': %s", file_path, id, my_error->message); g_propagate_error (error, g_steal_pointer (&my_error)); goto out; } @@ -1757,7 +1758,7 @@ system_locations_from_configuration (GCancellable *cancellable, if (!g_file_test (config_dir, G_FILE_TEST_IS_DIR)) { - g_debug ("No installations directory in %s. Skipping", config_dir); + g_info ("No installations directory in %s. Skipping", config_dir); goto out; } @@ -1768,8 +1769,8 @@ system_locations_from_configuration (GCancellable *cancellable, cancellable, &my_error); if (my_error != NULL) { - g_debug ("Unexpected error retrieving extra installations in %s: %s", - config_dir, my_error->message); + g_info ("Unexpected error retrieving extra installations in %s: %s", + config_dir, my_error->message); g_propagate_error (error, g_steal_pointer (&my_error)); goto out; } @@ -1784,8 +1785,8 @@ system_locations_from_configuration (GCancellable *cancellable, if (!g_file_enumerator_iterate (dir_enum, &file_info, &path, cancellable, &my_error)) { - g_debug ("Unexpected error reading file in %s: %s", - config_dir, my_error->message); + g_info ("Unexpected error reading file in %s: %s", + config_dir, my_error->message); g_propagate_error (error, g_steal_pointer (&my_error)); goto out; } @@ -2172,7 +2173,7 @@ flatpak_dir_system_helper_call (FlatpakDir *self, return NULL; } - g_debug ("Calling system helper: %s", method_name); + g_info ("Calling system helper: %s", method_name); res = g_dbus_connection_call_with_unix_fd_list_sync (self->system_helper_bus, FLATPAK_SYSTEM_HELPER_BUS_NAME, FLATPAK_SYSTEM_HELPER_PATH, @@ -2477,7 +2478,7 @@ flatpak_dir_system_helper_call_cancel_pull (FlatpakDir *self, if (flatpak_dir_get_no_interaction (self)) arg_flags |= FLATPAK_HELPER_CANCEL_PULL_FLAGS_NO_INTERACTION; - g_debug ("Calling system helper: CancelPull"); + g_info ("Calling system helper: CancelPull"); g_autoptr(GVariant) ret = flatpak_dir_system_helper_call (self, "CancelPull", @@ -2507,7 +2508,7 @@ flatpak_dir_system_helper_call_get_revokefs_fd (FlatpakDir *self, if (flatpak_dir_get_no_interaction (self)) arg_flags |= FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_NO_INTERACTION; - g_debug ("Calling system helper: GetRevokefsFd"); + g_info ("Calling system helper: GetRevokefsFd"); g_autoptr(GVariant) ret = flatpak_dir_system_helper_call (self, "GetRevokefsFd", @@ -2586,7 +2587,7 @@ flatpak_dir_finalize (GObject *object) if (self->system_helper_bus != (gpointer) 1) g_clear_object (&self->system_helper_bus); - g_clear_object (&self->soup_session); + g_clear_pointer (&self->http_session, flatpak_http_session_free); g_clear_pointer (&self->summary_cache, g_hash_table_unref); g_clear_pointer (&self->remote_filters, g_hash_table_unref); g_clear_pointer (&self->masked, g_regex_unref); @@ -3789,7 +3790,7 @@ flatpak_dir_migrate_config (FlatpakDir *self, if (config == NULL) config = ostree_repo_copy_config (flatpak_dir_get_repo (self)); - g_debug ("Migrating remote '%s' to gpg-verify-summary", remote); + g_info ("Migrating remote '%s' to gpg-verify-summary", remote); g_key_file_set_boolean (config, group, "gpg-verify-summary", TRUE); } } @@ -3805,7 +3806,7 @@ flatpak_dir_migrate_config (FlatpakDir *self, FLATPAK_HELPER_ENSURE_REPO_FLAGS_NONE, installation ? installation : "", NULL, &local_error)) - g_debug ("Failed to migrate system config: %s", local_error->message); + g_info ("Failed to migrate system config: %s", local_error->message); } else { @@ -3915,8 +3916,8 @@ _flatpak_dir_find_new_flatpakrepos (FlatpakDir *self, OstreeRepo *repo) if (!g_file_enumerator_iterate (dir_enum, &file_info, &path, NULL, &my_error)) { - g_debug ("Unexpected error reading file in %s: %s", - config_dir, my_error->message); + g_info ("Unexpected error reading file in %s: %s", + config_dir, my_error->message); break; } @@ -4004,7 +4005,7 @@ apply_new_flatpakrepo (const char *remote_name, NULL, &imported, NULL, error)) return FALSE; - g_debug ("Imported %u GPG key%s to remote \"%s\"", imported, (imported == 1) ? "" : "s", remote_name); + g_info ("Imported %u GPG key%s to remote \"%s\"", imported, (imported == 1) ? "" : "s", remote_name); } return TRUE; @@ -4557,6 +4558,67 @@ remove_old_appstream_tmpdirs (GFile *dir) tmp = g_file_get_child (dir, dent->d_name); /* We ignore errors here, no need to worry anyone */ + g_info ("Deleting stale appstream deploy tmpdir %s", flatpak_file_get_path_cached (tmp)); + (void)flatpak_rm_rf (tmp, NULL, NULL); + } +} + +/* Like the function above, this looks for old temporary directories created by + * previous versions of flatpak_dir_deploy(). + * These are all directories starting with a dot. Such directories can be from a + * concurrent deploy, so we only remove directories older than a day to avoid + * races. +*/ +static void +remove_old_deploy_tmpdirs (GFile *dir) +{ + g_auto(GLnxDirFdIterator) dir_iter = { 0 }; + time_t now = time (NULL); + + if (!glnx_dirfd_iterator_init_at (AT_FDCWD, flatpak_file_get_path_cached (dir), + FALSE, &dir_iter, NULL)) + return; + + while (TRUE) + { + struct stat stbuf; + struct dirent *dent; + g_autoptr(GFile) tmp = NULL; + + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dir_iter, &dent, NULL, NULL)) + break; + + if (dent == NULL) + break; + + /* We ignore non-dotfiles and .timestamps as they are not tempfiles */ + if (dent->d_name[0] != '.' || + strcmp (dent->d_name, ".timestamp") == 0) + continue; + + /* Check for right types and names. The format we’re looking for is: + * .[0-9a-f]{64}-[0-9A-Z]{6} */ + if (dent->d_type == DT_DIR) + { + if (strlen (dent->d_name) != 72 || + dent->d_name[65] != '-') + continue; + } + else + continue; + + /* Check that the file is at least a day old to avoid races */ + if (!glnx_fstatat (dir_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW, NULL)) + continue; + + if (stbuf.st_mtime >= now || + now - stbuf.st_mtime < SECS_PER_DAY) + continue; + + tmp = g_file_get_child (dir, dent->d_name); + + /* We ignore errors here, no need to worry anyone */ + g_info ("Deleting stale deploy tmpdir %s", flatpak_file_get_path_cached (tmp)); (void)flatpak_rm_rf (tmp, NULL, NULL); } } @@ -4941,7 +5003,7 @@ flatpak_dir_update_oci_index (FlatpakDir *self, if (index_cache == NULL) return NULL; - ensure_soup_session (self); + ensure_http_session (self); if (!ostree_repo_remote_get_url (self->repo, remote, @@ -4949,7 +5011,7 @@ flatpak_dir_update_oci_index (FlatpakDir *self, error)) return NULL; - if (!flatpak_oci_index_ensure_cached (self->soup_session, oci_uri, + if (!flatpak_oci_index_ensure_cached (self->http_session, oci_uri, index_cache, index_uri_out, cancellable, &local_error)) { @@ -5047,9 +5109,9 @@ flatpak_dir_update_appstream_oci (FlatpakDir *self, FALSE, &icons_dfd, error)) return FALSE; - ensure_soup_session (self); + ensure_http_session (self); - appstream = flatpak_oci_index_make_appstream (self->soup_session, + appstream = flatpak_oci_index_make_appstream (self->http_session, index_cache, index_uri, arch, @@ -5405,7 +5467,7 @@ repo_pull (OstreeRepo *self, sideload_url = g_file_get_uri (sideload_repo); - g_debug ("Sideloading %s from %s in pull", ref_to_fetch, sideload_url); + g_info ("Sideloading %s from %s in pull", ref_to_fetch, sideload_url); g_assert (state->collection_id != NULL); @@ -5480,15 +5542,15 @@ repo_pull (OstreeRepo *self, } static void -ensure_soup_session (FlatpakDir *self) +ensure_http_session (FlatpakDir *self) { - if (g_once_init_enter (&self->soup_session)) + if (g_once_init_enter (&self->http_session)) { - SoupSession *soup_session; + FlatpakHttpSession *http_session; - soup_session = flatpak_create_soup_session (PACKAGE_STRING); + http_session = flatpak_create_http_session (PACKAGE_STRING); - g_once_init_leave (&self->soup_session, soup_session); + g_once_init_leave (&self->http_session, http_session); } } @@ -5676,7 +5738,7 @@ flatpak_dir_pull_extra_data (FlatpakDir *self, extra_local_file = flatpak_build_file (base_dir, "extra-data", extra_data_sha256, extra_data_name, NULL); if (g_file_query_exists (extra_local_file, cancellable)) { - g_debug ("Loading extra-data from local file %s", flatpak_file_get_path_cached (extra_local_file)); + g_info ("Loading extra-data from local file %s", flatpak_file_get_path_cached (extra_local_file)); gsize extra_local_size; g_autofree char *extra_local_contents = NULL; g_autoptr(GError) my_error = NULL; @@ -5691,8 +5753,8 @@ flatpak_dir_pull_extra_data (FlatpakDir *self, } else { - ensure_soup_session (self); - bytes = flatpak_load_uri (self->soup_session, extra_data_uri, 0, NULL, + ensure_http_session (self); + bytes = flatpak_load_uri (self->http_session, extra_data_uri, 0, NULL, extra_data_progress_report, progress, NULL, cancellable, error); } @@ -5827,7 +5889,7 @@ flatpak_dir_mirror_oci (FlatpakDir *self, flatpak_progress_start_oci_pull (progress); - g_debug ("Mirroring OCI image %s", oci_digest); + g_info ("Mirroring OCI image %s", oci_digest); res = flatpak_mirror_image_from_oci (dst_registry, registry, oci_repository, oci_digest, state->remote_name, ref, delta_url, self->repo, oci_pull_progress_cb, progress, cancellable, error); @@ -5908,7 +5970,7 @@ flatpak_dir_pull_oci (FlatpakDir *self, flatpak_progress_start_oci_pull (progress); - g_debug ("Pulling OCI image %s", oci_digest); + g_info ("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); @@ -5916,7 +5978,7 @@ flatpak_dir_pull_oci (FlatpakDir *self, if (checksum == NULL) return FALSE; - g_debug ("Imported OCI image as checksum %s", checksum); + g_info ("Imported OCI image as checksum %s", checksum); if (repo == self->repo) name = flatpak_dir_get_name (self); @@ -5996,11 +6058,11 @@ flatpak_dir_pull (FlatpakDir *self, return FALSE; } - g_debug ("%s: Using commit %s for pull of ref %s from remote %s%s%s", - G_STRFUNC, rev, ref, state->remote_name, - sideload_repo ? "sideloaded from " : "", - sideload_repo ? flatpak_file_get_path_cached (sideload_repo) : "" - ); + g_info ("%s: Using commit %s for pull of ref %s from remote %s%s%s", + G_STRFUNC, rev, ref, state->remote_name, + sideload_repo ? "sideloaded from " : "", + sideload_repo ? flatpak_file_get_path_cached (sideload_repo) : "" + ); if (repo == NULL) repo = self->repo; @@ -6442,6 +6504,7 @@ flatpak_dir_make_current_ref (FlatpakDir *self, GCancellable *cancellable, GError **error) { + g_autoptr(GError) local_error = NULL; g_autoptr(GFile) base = NULL; g_autoptr(GFile) dir = NULL; g_autoptr(GFile) current_link = NULL; @@ -6458,7 +6521,12 @@ flatpak_dir_make_current_ref (FlatpakDir *self, current_link = g_file_get_child (dir, "current"); - g_file_delete (current_link, cancellable, NULL); + if (!g_file_delete (current_link, cancellable, &local_error) && + !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } rest = flatpak_decomposed_peek_arch (ref, NULL); if (!g_file_make_symbolic_link (current_link, rest, cancellable, error)) @@ -6684,32 +6752,185 @@ flatpak_dir_list_refs (FlatpakDir *self, return g_steal_pointer (&refs); } -GPtrArray * -flatpak_dir_list_app_refs_with_runtime (FlatpakDir *self, - FlatpakDecomposed *runtime_ref, - GCancellable *cancellable, - GError **error) +gboolean +flatpak_dir_is_runtime_extension (FlatpakDir *self, + FlatpakDecomposed *ref) { + g_autoptr(GBytes) ext_deploy_data = NULL; + + if (!flatpak_decomposed_is_runtime (ref)) + return FALSE; + + /* deploy v4 guarantees extension-of info */ + ext_deploy_data = flatpak_dir_get_deploy_data (self, ref, 4, NULL, NULL); + if (ext_deploy_data && flatpak_deploy_data_get_extension_of (ext_deploy_data) != NULL) + return TRUE; + + return FALSE; +} + +static GHashTable * +flatpak_dir_get_runtime_app_map (FlatpakDir *self, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GHashTable) runtime_app_map = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, + (GEqualFunc)flatpak_decomposed_equal, + (GDestroyNotify)flatpak_decomposed_unref, + (GDestroyNotify)g_ptr_array_unref); g_autoptr(GPtrArray) app_refs = NULL; - const char *runtime_pref = flatpak_decomposed_get_pref (runtime_ref); - g_autoptr(GPtrArray) apps = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref); - app_refs = flatpak_dir_list_refs (self, FLATPAK_KINDS_APP, NULL, NULL); - for (int i = 0; app_refs != NULL && i < app_refs->len; i++) + app_refs = flatpak_dir_list_refs (self, FLATPAK_KINDS_APP, cancellable, error); + if (app_refs == NULL) + return NULL; + + for (guint i = 0; i < app_refs->len; i++) { FlatpakDecomposed *app_ref = g_ptr_array_index (app_refs, i); /* deploy v4 guarantees runtime info */ g_autoptr(GBytes) app_deploy_data = flatpak_dir_get_deploy_data (self, app_ref, 4, NULL, NULL); + g_autoptr(FlatpakDecomposed) runtime_decomposed = NULL; + g_autoptr(GPtrArray) runtime_apps = NULL; + const char *runtime_pref; + + if (app_deploy_data == NULL) + continue; + + runtime_pref = flatpak_deploy_data_get_runtime (app_deploy_data); + runtime_decomposed = flatpak_decomposed_new_from_pref (FLATPAK_KINDS_RUNTIME, runtime_pref, error); + if (runtime_decomposed == NULL) + return NULL; + + runtime_apps = g_hash_table_lookup (runtime_app_map, runtime_decomposed); + if (runtime_apps == NULL) + { + runtime_apps = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref); + g_hash_table_insert (runtime_app_map, flatpak_decomposed_ref (runtime_decomposed), g_ptr_array_ref (runtime_apps)); + } + else + g_ptr_array_ref (runtime_apps); + + g_ptr_array_add (runtime_apps, flatpak_decomposed_ref (app_ref)); + } - if (app_deploy_data) + return g_steal_pointer (&runtime_app_map); +} + +GPtrArray * +flatpak_dir_list_app_refs_with_runtime (FlatpakDir *self, + GHashTable **runtime_app_map, + FlatpakDecomposed *runtime_ref, + GCancellable *cancellable, + GError **error) +{ + GPtrArray *apps; + + g_assert (runtime_app_map != NULL); + + if (*runtime_app_map == NULL) + *runtime_app_map = flatpak_dir_get_runtime_app_map (self, cancellable, error); + + if (*runtime_app_map == NULL) + return NULL; + + apps = g_hash_table_lookup (*runtime_app_map, runtime_ref); + if (apps == NULL) /* unused runtime */ + return g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref); + + return g_ptr_array_ref (apps); +} + +static GHashTable * +flatpak_dir_get_extension_app_map (FlatpakDir *self, + GHashTable *runtime_app_map, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GHashTable) extension_app_map = g_hash_table_new_full ((GHashFunc)flatpak_decomposed_hash, + (GEqualFunc)flatpak_decomposed_equal, + (GDestroyNotify)flatpak_decomposed_unref, + (GDestroyNotify)g_ptr_array_unref); + g_autoptr(GPtrArray) all_refs = NULL; + + g_assert (runtime_app_map != NULL); + + all_refs = flatpak_dir_list_refs (self, FLATPAK_KINDS_RUNTIME | FLATPAK_KINDS_APP, NULL, NULL); + for (guint i = 0; all_refs != NULL && i < all_refs->len; i++) + { + FlatpakDecomposed *ref = g_ptr_array_index (all_refs, i); + g_autoptr(GPtrArray) related = NULL; + GPtrArray *runtime_apps = NULL; + + if (flatpak_decomposed_id_is_subref (ref)) + continue; + + if (flatpak_decomposed_is_runtime (ref)) + { + runtime_apps = g_hash_table_lookup (runtime_app_map, ref); + if (runtime_apps == NULL) + continue; + } + + related = flatpak_dir_find_local_related (self, ref, NULL, TRUE, cancellable, error); + if (related == NULL) + return NULL; + + for (guint j = 0; j < related->len; j++) { - const char *app_runtime = flatpak_deploy_data_get_runtime (app_deploy_data); - if (g_strcmp0 (app_runtime, runtime_pref) == 0) - g_ptr_array_add (apps, flatpak_decomposed_ref (app_ref)); + FlatpakRelated *rel = g_ptr_array_index (related, j); + g_autoptr(GPtrArray) extension_apps = g_hash_table_lookup (extension_app_map, rel->ref); + if (extension_apps == NULL) + { + extension_apps = g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref); + g_hash_table_insert (extension_app_map, flatpak_decomposed_ref (rel->ref), g_ptr_array_ref (extension_apps)); + } + else + g_ptr_array_ref (extension_apps); + + if (flatpak_decomposed_is_runtime (ref)) + { + g_assert (runtime_apps); + for (guint k = 0; runtime_apps && k < runtime_apps->len; k++) + g_ptr_array_add (extension_apps, flatpak_decomposed_ref (g_ptr_array_index (runtime_apps, k))); + } + else + g_ptr_array_add (extension_apps, flatpak_decomposed_ref (ref)); } } - return g_steal_pointer (&apps); + return g_steal_pointer (&extension_app_map); +} + +GPtrArray * +flatpak_dir_list_app_refs_with_runtime_extension (FlatpakDir *self, + GHashTable **runtime_app_map, + GHashTable **extension_app_map, + FlatpakDecomposed *runtime_ext_ref, + GCancellable *cancellable, + GError **error) +{ + GPtrArray *apps; + + g_assert (runtime_app_map != NULL); + g_assert (extension_app_map != NULL); + + if (*runtime_app_map == NULL) + *runtime_app_map = flatpak_dir_get_runtime_app_map (self, cancellable, error); + + if (*runtime_app_map == NULL) + return NULL; + + if (*extension_app_map == NULL) + *extension_app_map = flatpak_dir_get_extension_app_map (self, *runtime_app_map, cancellable, error); + + if (*extension_app_map == NULL) + return NULL; + + apps = g_hash_table_lookup (*extension_app_map, runtime_ext_ref); + if (apps == NULL) /* unused extension */ + return g_ptr_array_new_with_free_func ((GDestroyNotify)flatpak_decomposed_unref); + + return g_ptr_array_ref (apps); } GVariant * @@ -6865,7 +7086,7 @@ flatpak_dir_run_triggers (FlatpakDir *self, if (triggerspath == NULL) triggerspath = FLATPAK_TRIGGERDIR; - g_debug ("running triggers from %s", triggerspath); + g_info ("running triggers from %s", triggerspath); triggersdir = g_file_new_for_path (triggerspath); @@ -6895,7 +7116,7 @@ flatpak_dir_run_triggers (FlatpakDir *self, g_autoptr(FlatpakBwrap) bwrap = NULL; g_autofree char *commandline = NULL; - g_debug ("running trigger %s", name); + g_info ("running trigger %s", name); bwrap = flatpak_bwrap_new (NULL); @@ -6918,7 +7139,7 @@ flatpak_dir_run_triggers (FlatpakDir *self, flatpak_bwrap_finish (bwrap); commandline = flatpak_quote_argv ((const char **) bwrap->argv->pdata, -1); - g_debug ("Running '%s'", commandline); + g_info ("Running '%s'", commandline); /* We use LEAVE_DESCRIPTORS_OPEN to work around dead-lock, see flatpak_close_fds_workaround */ if (!g_spawn_sync ("/", @@ -7875,7 +8096,7 @@ extract_extra_data (FlatpakDir *self, if (n_extra_data_sources == 0) return TRUE; - g_debug ("extracting extra data to %s", flatpak_file_get_path_cached (extradir)); + g_info ("extracting extra data to %s", flatpak_file_get_path_cached (extradir)); if (!ostree_repo_read_commit_detached_metadata (self->repo, checksum, &detached_metadata, cancellable, error)) @@ -8029,6 +8250,7 @@ apply_extra_data (FlatpakDir *self, int exit_status; const char *group = FLATPAK_METADATA_GROUP_APPLICATION; g_autoptr(GError) local_error = NULL; + FlatpakRunFlags run_flags; apply_extra_file = g_file_resolve_relative_path (checkoutdir, "files/bin/apply_extra"); if (!g_file_query_exists (apply_extra_file, cancellable)) @@ -8105,20 +8327,22 @@ apply_extra_data (FlatpakDir *self, "--cap-drop", "ALL", NULL); + /* Might need multiarch in apply_extra (see e.g. #3742). + * Should be pretty safe in this limited context */ + run_flags = (FLATPAK_RUN_FLAG_MULTIARCH | + FLATPAK_RUN_FLAG_NO_SESSION_HELPER | + FLATPAK_RUN_FLAG_NO_PROC | + FLATPAK_RUN_FLAG_NO_SESSION_BUS_PROXY | + FLATPAK_RUN_FLAG_NO_SYSTEM_BUS_PROXY | + FLATPAK_RUN_FLAG_NO_A11Y_BUS_PROXY); + 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)) + run_flags, 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, + if (!flatpak_run_add_environment_args (bwrap, NULL, run_flags, id, app_context, NULL, NULL, -1, NULL, cancellable, error)) return FALSE; @@ -8131,7 +8355,7 @@ apply_extra_data (FlatpakDir *self, flatpak_bwrap_finish (bwrap); - g_debug ("Running /app/bin/apply_extra "); + g_info ("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 @@ -8219,7 +8443,7 @@ flatpak_dir_check_parental_controls (FlatpakDir *self, * system-helper, self->source_pid is non-zero. */ if (self->source_pid == 0 && getuid () == 0) { - g_debug ("Skipping parental controls check for %s due to running as root", ref); + g_info ("Skipping parental controls check for %s due to running as root", ref); return TRUE; } @@ -8229,7 +8453,7 @@ flatpak_dir_check_parental_controls (FlatpakDir *self, if (!g_str_has_prefix (ref, "app/")) return TRUE; - g_debug ("Getting parental controls details for %s from %s", + g_info ("Getting parental controls details for %s from %s", ref, flatpak_deploy_data_get_origin (deploy_data)); if (on_session != NULL) @@ -8237,8 +8461,8 @@ flatpak_dir_check_parental_controls (FlatpakDir *self, /* 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); + g_info ("Skipping parental controls check for %s since the " + "system bus is unavailable in the test environment", ref); return TRUE; } @@ -8272,15 +8496,15 @@ flatpak_dir_check_parental_controls (FlatpakDir *self, 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); + g_info ("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); + g_info ("Skipping parental controls check for %s since a required " + "service was not found", ref); return TRUE; } else if (local_error != NULL) @@ -8301,7 +8525,7 @@ flatpak_dir_check_parental_controls (FlatpakDir *self, if (repo_installation_allowed && app_is_appropriate) { - g_debug ("Parental controls policy satisfied for %s", ref); + g_info ("Parental controls policy satisfied for %s", ref); return TRUE; } @@ -8332,7 +8556,7 @@ flatpak_dir_check_parental_controls (FlatpakDir *self, _("Installing %s is not allowed by the policy set by your administrator"), ref); - g_debug ("Parental controls policy overridden by polkit for %s", ref); + g_info ("Parental controls policy overridden by polkit for %s", ref); #endif /* USE_SYSTEM_HELPER */ #endif /* HAVE_LIBMALCONTENT */ @@ -8369,9 +8593,11 @@ flatpak_dir_deploy (FlatpakDir *self, g_autofree char *ref_id = NULL; g_autoptr(GFile) root = NULL; g_autoptr(GFile) deploy_base = NULL; + glnx_autofd int deploy_base_dfd = -1; g_autoptr(GFile) checkoutdir = NULL; g_autoptr(GFile) bindir = NULL; g_autofree char *checkoutdirpath = NULL; + const char *checkoutdir_basename; g_autoptr(GFile) real_checkoutdir = NULL; g_autoptr(GFile) dotref = NULL; g_autoptr(GFile) files_etc = NULL; @@ -8385,8 +8611,6 @@ flatpak_dir_deploy (FlatpakDir *self, 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; @@ -8396,6 +8620,7 @@ flatpak_dir_deploy (FlatpakDir *self, g_autofree char *metadata_contents = NULL; gsize metadata_size = 0; const char *flatpak; + g_auto(GLnxTmpDir) tmp_dir_handle = { 0, }; if (!flatpak_dir_ensure_repo (self, cancellable, error)) return FALSE; @@ -8410,9 +8635,18 @@ flatpak_dir_deploy (FlatpakDir *self, deploy_base = flatpak_dir_get_deploy_dir (self, ref); + if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (deploy_base), TRUE, &deploy_base_dfd, error)) + return FALSE; + + /* There used to be a bug here where temporary files beneath @deploy_base were not removed, + * which could use quite a lot of space over time, so we check for these and remove them. + * We only do so for the current app to avoid every deploy operation iterating over + * every app directory and all their immediate descendents. That would be a bit much I/O. */ + remove_old_deploy_tmpdirs (deploy_base); + if (checksum_or_latest == NULL) { - g_debug ("No checksum specified, getting tip of %s from origin %s", flatpak_decomposed_get_ref (ref), origin); + g_info ("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) @@ -8422,12 +8656,12 @@ flatpak_dir_deploy (FlatpakDir *self, } checksum = resolved_ref; - g_debug ("tip resolved to: %s", checksum); + g_info ("tip resolved to: %s", checksum); } else { checksum = checksum_or_latest; - g_debug ("Looking for checksum %s in local repo", checksum); + g_info ("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)); } @@ -8444,17 +8678,15 @@ flatpak_dir_deploy (FlatpakDir *self, _("%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) + if (!glnx_mkdtempat (deploy_base_dfd, template, 0755, &tmp_dir_handle, 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); + checkoutdir = g_file_get_child (deploy_base, tmp_dir_handle.path); if (!ostree_repo_read_commit (self->repo, checksum, &root, NULL, cancellable, error)) { @@ -8470,11 +8702,12 @@ flatpak_dir_deploy (FlatpakDir *self, 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); + checkoutdir_basename = tmp_dir_handle.path; /* so checkoutdirpath = deploy_base_dfd / checkoutdir_basename */ if (subpaths == NULL || *subpaths == NULL) { if (!ostree_repo_checkout_at (self->repo, &options, - AT_FDCWD, checkoutdirpath, + deploy_base_dfd, checkoutdir_basename, checksum, cancellable, error)) { @@ -8493,7 +8726,7 @@ flatpak_dir_deploy (FlatpakDir *self, options.subpath = "metadata"; if (!ostree_repo_checkout_at (self->repo, &options, - AT_FDCWD, checkoutdirpath, + deploy_base_dfd, checkoutdir_basename, checksum, cancellable, error)) { @@ -8506,13 +8739,14 @@ flatpak_dir_deploy (FlatpakDir *self, 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_autofree char *dstpath_relative_to_deploy_base = g_build_filename (checkoutdir_basename, "/files", subpaths[i], NULL); 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]); + g_info ("subpath %s not in tree", subpaths[i]); continue; } @@ -8524,7 +8758,7 @@ flatpak_dir_deploy (FlatpakDir *self, options.subpath = subpath; if (!ostree_repo_checkout_at (self->repo, &options, - AT_FDCWD, dstpath, + deploy_base_dfd, dstpath_relative_to_deploy_base, checksum, cancellable, error)) { @@ -8740,7 +8974,7 @@ flatpak_dir_deploy (FlatpakDir *self, if (!flatpak_bytes_save (deploy_data_file, deploy_data, cancellable, error)) return FALSE; - if (!glnx_opendirat (AT_FDCWD, checkoutdirpath, TRUE, &checkoutdir_dfd, error)) + if (!glnx_opendirat (deploy_base_dfd, checkoutdir_basename, TRUE, &checkoutdir_dfd, error)) return FALSE; if (syncfs (checkoutdir_dfd) != 0) @@ -8753,6 +8987,8 @@ flatpak_dir_deploy (FlatpakDir *self, cancellable, NULL, NULL, error)) return FALSE; + glnx_tmpdir_unset (&tmp_dir_handle); + if (!flatpak_dir_set_active (self, ref, checkout_basename, cancellable, error)) return FALSE; @@ -8836,7 +9072,7 @@ flatpak_dir_deploy_install (FlatpakDir *self, if (strcmp (old_origin, origin) != 0) remove_ref_from_remote = g_strdup (old_origin); - g_debug ("Removing old deployment for reinstall"); + g_info ("Removing old deployment for reinstall"); if (!flatpak_dir_undeploy (self, ref, old_active, TRUE, FALSE, cancellable, error)) @@ -9035,7 +9271,7 @@ rewrite_one_dynamic_launcher (const char *portal_desktop_dir, } if (!g_key_file_has_key (old_key_file, G_KEY_FILE_DESKTOP_GROUP, "X-Flatpak", NULL)) { - g_debug ("Ignoring non-Flatpak dynamic launcher: %s", desktop_path); + g_info ("Ignoring non-Flatpak dynamic launcher: %s", desktop_path); return; } @@ -9069,7 +9305,13 @@ rewrite_one_dynamic_launcher (const char *portal_desktop_dir, /* Fix symlink */ link_file = g_file_new_build_filename (g_get_user_data_dir (), "applications", desktop_name, NULL); relative_path = g_build_filename ("..", "xdg-desktop-portal", "applications", new_desktop, NULL); - g_file_delete (link_file, NULL, NULL); + if (!g_file_delete (link_file, NULL, &local_error) && + !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_info ("Unable to delete desktop file link %s: %s", desktop_name, local_error->message); + g_clear_error (&local_error); + } + new_link_file = g_file_new_build_filename (g_get_user_data_dir (), "applications", new_desktop, NULL); if (!g_file_make_symbolic_link (new_link_file, relative_path, NULL, &local_error)) { @@ -9414,7 +9656,7 @@ flatpak_dir_setup_revokefs_fuse_mount (FlatpakDir *self, &local_error)) { if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED)) - g_debug ("revokefs-fuse not supported on your installation: %s", local_error->message); + g_info ("revokefs-fuse not supported on your installation: %s", local_error->message); else g_warning ("Failed to get revokefs-fuse socket from system-helper: %s", local_error->message); @@ -10512,10 +10754,11 @@ flatpak_dir_uninstall (FlatpakDir *self, if (flatpak_decomposed_is_runtime (ref) && !force_remove) { + g_autoptr(GHashTable) runtime_app_map = NULL; g_autoptr(GPtrArray) blocking = NULL; /* Look for apps that need this runtime */ - blocking = flatpak_dir_list_app_refs_with_runtime (self, ref, cancellable, error); + blocking = flatpak_dir_list_app_refs_with_runtime (self, &runtime_app_map, ref, cancellable, error); if (blocking == NULL) return FALSE; @@ -10538,7 +10781,7 @@ flatpak_dir_uninstall (FlatpakDir *self, old_active = g_strdup (flatpak_deploy_data_get_commit (deploy_data)); - g_debug ("dropping active ref"); + g_info ("dropping active ref"); if (!flatpak_dir_set_active (self, ref, NULL, cancellable, error)) return FALSE; @@ -10548,7 +10791,7 @@ flatpak_dir_uninstall (FlatpakDir *self, if (current_ref != NULL && flatpak_decomposed_equal (ref, current_ref)) { - g_debug ("dropping current ref"); + g_info ("dropping current ref"); if (!flatpak_dir_drop_current_ref (self, name, cancellable, error)) return FALSE; } @@ -10965,7 +11208,7 @@ flatpak_dir_undeploy_all (FlatpakDir *self, for (i = 0; deployed[i] != NULL; i++) { - g_debug ("undeploying %s", deployed[i]); + g_info ("undeploying %s", deployed[i]); if (!flatpak_dir_undeploy (self, ref, deployed[i], FALSE, force_remove, cancellable, error)) return FALSE; } @@ -10974,12 +11217,12 @@ flatpak_dir_undeploy_all (FlatpakDir *self, was_deployed = g_file_query_exists (deploy_base, cancellable); if (was_deployed) { - g_debug ("removing deploy base"); + g_info ("removing deploy base"); if (!flatpak_rm_rf (deploy_base, cancellable, error)) return FALSE; } - g_debug ("cleaning up empty directories"); + g_info ("cleaning up empty directories"); arch_dir = g_file_get_parent (deploy_base); if (g_file_query_exists (arch_dir, cancellable) && !g_file_delete (arch_dir, cancellable, &temp_error)) @@ -11149,7 +11392,7 @@ flatpak_dir_prune (FlatpakDir *self, the shared lock operation is released and we will do a prune then */ if (g_error_matches (lock_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { - g_debug ("Skipping prune due to in progress operation"); + g_info ("Skipping prune due to in progress operation"); return TRUE; } @@ -11157,7 +11400,7 @@ flatpak_dir_prune (FlatpakDir *self, return FALSE; } - g_debug ("Pruning repo"); + g_info ("Pruning repo"); if (!ostree_repo_prune (self->repo, OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY, 0, @@ -11168,7 +11411,7 @@ flatpak_dir_prune (FlatpakDir *self, goto out; formatted_freed_size = g_format_size_full (pruned_object_size_total, 0); - g_debug ("Pruned %d/%d objects, size %s", objects_total, objects_pruned, formatted_freed_size); + g_info ("Pruned %d/%d objects, size %s", objects_total, objects_pruned, formatted_freed_size); ret = TRUE; @@ -11208,7 +11451,7 @@ flatpak_dir_update_summary (FlatpakDir *self, g_autoptr(GError) local_error = NULL; g_autoptr(GFile) summary_file = NULL; - g_debug ("Deleting summary"); + g_info ("Deleting summary"); summary_file = g_file_get_child (ostree_repo_get_path (self->repo), "summary"); @@ -11224,7 +11467,7 @@ flatpak_dir_update_summary (FlatpakDir *self, { g_auto(GLnxLockFile) lock = { 0, }; - g_debug ("Updating summary"); + g_info ("Updating summary"); /* Keep a shared repo lock to avoid prunes removing objects we're relying on * while generating the summary. */ @@ -11526,7 +11769,7 @@ flatpak_dir_lookup_cached_summary (FlatpakDir *self, if ((now - summary->time) / G_USEC_PER_SEC < SUMMARY_CACHE_TIMEOUT_SEC && strcmp (url, summary->url) == 0) { - /* g_debug ("Using cached summary for remote %s", name); */ + /* g_info ("Using cached summary for remote %s", name); */ *bytes_out = g_bytes_ref (summary->bytes); if (bytes_sig_out) { @@ -11800,7 +12043,7 @@ flatpak_dir_remote_clear_cached_summary (FlatpakDir *self, GCancellable *cancellable, GError **error) { - g_debug ("Clearing cached summaries for remote %s", remote); + g_info ("Clearing cached summaries for remote %s", remote); if (!_flatpak_dir_remote_clear_cached_summary (self, remote, NULL, cancellable, error)) return FALSE; if (!_flatpak_dir_remote_clear_cached_summary (self, remote, ".sig", cancellable, error)) @@ -11902,9 +12145,26 @@ flatpak_dir_remote_load_cached_summary (FlatpakDir *self, sha256 = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, mfile_bytes); if (strcmp (sha256, checksum) != 0) { - g_file_delete (main_cache_file, NULL, NULL); + g_autoptr(GError) local_error = NULL; + + if (!g_file_delete (main_cache_file, NULL, &local_error) && + !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_autofree char *path = g_file_get_path (main_cache_file); + g_info ("Unable to delete file %s: %s", path, local_error->message); + g_clear_error (&local_error); + } + if (sig_ext) - g_file_delete (sig_cache_file, NULL, NULL); + { + if (!g_file_delete (sig_cache_file, NULL, &local_error) && + !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_autofree char *path = g_file_get_path (sig_cache_file); + g_info ("Unable to delete file %s: %s", path, local_error->message); + g_clear_error (&local_error); + } + } return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid checksum for indexed summary %s read from %s"), @@ -11966,11 +12226,11 @@ flatpak_dir_remote_fetch_summary (FlatpakDir *self, if (!flatpak_dir_remote_load_cached_summary (self, name_or_uri, NULL, NULL, ".sig", &summary, &summary_sig, cancellable, error)) return FALSE; - g_debug ("Loaded summary from cache for remote ‘%s’", name_or_uri); + g_info ("Loaded summary from cache for remote ‘%s’", name_or_uri); } else { - g_debug ("Fetching summary file for remote ‘%s’", name_or_uri); + g_info ("Fetching summary file for remote ‘%s’", name_or_uri); if (!ostree_repo_remote_fetch_summary (self->repo, name_or_uri, &summary, &summary_sig, cancellable, @@ -12045,7 +12305,7 @@ remote_verify_signature (OstreeRepo *repo, } static GBytes * -load_uri_with_fallback (SoupSession *soup_session, +load_uri_with_fallback (FlatpakHttpSession *http_session, const char *uri, const char *uri2, FlatpakHTTPFlags flags, @@ -12056,7 +12316,7 @@ load_uri_with_fallback (SoupSession *soup_session, g_autoptr(GError) local_error = NULL; GBytes *res; - res = flatpak_load_uri (soup_session, uri, flags, token, + res = flatpak_load_uri (http_session, uri, flags, token, NULL, NULL, NULL, cancellable, &local_error); if (res) @@ -12068,7 +12328,7 @@ load_uri_with_fallback (SoupSession *soup_session, return NULL; } - return flatpak_load_uri (soup_session, uri2, flags, token, + return flatpak_load_uri (http_session, uri2, flags, token, NULL, NULL, NULL, cancellable, error); } @@ -12092,7 +12352,7 @@ flatpak_dir_remote_fetch_summary_index (FlatpakDir *self, g_autoptr(GBytes) index_sig = NULL; gboolean gpg_verify_summary; - ensure_soup_session (self); + ensure_http_session (self); if (!ostree_repo_remote_get_url (self->repo, name_or_uri, &url, error)) return FALSE; @@ -12130,7 +12390,7 @@ flatpak_dir_remote_fetch_summary_index (FlatpakDir *self, g_propagate_error (error, g_steal_pointer (&cache_error)); return FALSE; } - g_debug ("Loaded summary index from cache for remote ‘%s’", name_or_uri); + g_info ("Loaded summary index from cache for remote ‘%s’", name_or_uri); index = g_steal_pointer (&cached_index); if (gpg_verify_summary) @@ -12142,9 +12402,9 @@ flatpak_dir_remote_fetch_summary_index (FlatpakDir *self, g_autoptr(GBytes) dl_index = NULL; gboolean used_download = FALSE; - g_debug ("Fetching summary index file for remote ‘%s’", name_or_uri); + g_info ("Fetching summary index file for remote ‘%s’", name_or_uri); - dl_index = flatpak_load_uri (self->soup_session, index_url, 0, NULL, + dl_index = flatpak_load_uri (self->http_session, index_url, 0, NULL, NULL, NULL, NULL, cancellable, error); if (dl_index == NULL) @@ -12173,7 +12433,7 @@ flatpak_dir_remote_fetch_summary_index (FlatpakDir *self, 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, + dl_index_sig = load_uri_with_fallback (self->http_session, index_sig_url, index_sig_url2, 0, NULL, cancellable, &dl_sig_error); if (dl_index_sig == NULL) { @@ -12242,7 +12502,7 @@ flatpak_dir_remote_fetch_indexed_summary (FlatpakDir *self, g_autofree char *checksum = NULL; g_autofree char *cache_name = NULL; - ensure_soup_session (self); + ensure_http_session (self); if (!ostree_repo_remote_get_url (self->repo, name_or_uri, &url, error)) return FALSE; @@ -12313,13 +12573,13 @@ flatpak_dir_remote_fetch_indexed_summary (FlatpakDir *self, 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_info ("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, + g_autoptr(GBytes) delta = flatpak_load_uri (self->http_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); + g_info ("Failed to load delta, falling back: %s", delta_error->message); else { g_autoptr(GBytes) applied = flatpak_summary_apply_diff (old_summary, delta, &delta_error); @@ -12340,9 +12600,9 @@ flatpak_dir_remote_fetch_indexed_summary (FlatpakDir *self, 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_info ("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, + summary_z = flatpak_load_uri (self->http_session, subsummary_url, 0, NULL, NULL, NULL, NULL, cancellable, error); if (summary_z == NULL) @@ -12372,7 +12632,7 @@ flatpak_dir_remote_fetch_indexed_summary (FlatpakDir *self, } } else - g_debug ("Loaded indexed summary file %s from cache for remote ‘%s’", checksum, name_or_uri); + g_info ("Loaded indexed summary file %s from cache for remote ‘%s’", checksum, name_or_uri); /* Cache in memory */ if (!is_local && !only_cached) @@ -12508,7 +12768,7 @@ _flatpak_dir_get_remote_state (FlatpakDir *self, got_summary = TRUE; if (optional && !g_cancellable_is_cancelled (cancellable)) { - g_debug ("Failed to download optional summary index: %s", local_error->message); + g_info ("Failed to download optional summary index: %s", local_error->message); state->summary_fetch_error = g_steal_pointer (&local_error); } else @@ -12534,7 +12794,7 @@ _flatpak_dir_get_remote_state (FlatpakDir *self, { if (optional && !g_cancellable_is_cancelled (cancellable)) { - g_debug ("Failed to download optional summary: %s", local_error->message); + g_info ("Failed to download optional summary: %s", local_error->message); state->summary_fetch_error = g_steal_pointer (&local_error); } else @@ -12599,7 +12859,7 @@ _flatpak_dir_get_remote_state (FlatpakDir *self, 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"); + g_info ("Invalid checksum for digested summary, not using cache"); continue; } @@ -12749,7 +13009,7 @@ populate_hash_table_from_refs_map (GHashTable *ret_all_refs, continue; /* New timestamp is older, skip this commit */ } - new_timestamp = g_memdup (×tamp, sizeof (guint64)); + new_timestamp = g_memdup2 (×tamp, sizeof (guint64)); } g_hash_table_replace (ret_all_refs, g_steal_pointer (&decomposed), ostree_checksum_from_bytes (csum_bytes)); @@ -14216,18 +14476,27 @@ parse_ref_file (GKeyFile *keyfile, 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); + /* We have a hierarchy of keys for setting the collection ID, which all have + * the same effect. The only difference is which versions of Flatpak support + * them, and therefore what P2P implementation is enabled by them: + * DeploySideloadCollectionID: supported by Flatpak >= 1.12.8 (1.7.1 + * introduced sideload support but this key was added late) + * DeployCollectionID: supported by Flatpak >= 1.0.6 + * CollectionID: supported by Flatpak >= 0.9.8 + */ + collection_id = flatpak_keyfile_get_string_non_empty (keyfile, FLATPAK_REF_GROUP, + FLATPAK_REF_DEPLOY_SIDELOAD_COLLECTION_ID_KEY); - if (collection_id != NULL && *collection_id == '\0') - g_clear_pointer (&collection_id, g_free); if (collection_id == NULL) { - collection_id = g_key_file_get_string (keyfile, FLATPAK_REF_GROUP, - FLATPAK_REF_COLLECTION_ID_KEY, NULL); + collection_id = flatpak_keyfile_get_string_non_empty (keyfile, FLATPAK_REF_GROUP, + FLATPAK_REF_DEPLOY_COLLECTION_ID_KEY); + } + if (collection_id == NULL) + { + collection_id = flatpak_keyfile_get_string_non_empty (keyfile, FLATPAK_REF_GROUP, + FLATPAK_REF_COLLECTION_ID_KEY); } - if (collection_id != NULL && *collection_id == '\0') - g_clear_pointer (&collection_id, g_free); if (collection_id != NULL && gpg_data == NULL) return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Collection ID requires GPG key to be provided")); @@ -14682,8 +14951,8 @@ flatpak_dir_modify_remote (FlatpakDir *self, return FALSE; /* XXX If we ever add internationalization, use ngettext() here. */ - g_debug ("Imported %u GPG key%s to remote \"%s\"", - imported, (imported == 1) ? "" : "s", remote_name); + g_info ("Imported %u GPG key%s to remote \"%s\"", + imported, (imported == 1) ? "" : "s", remote_name); } filter_path = g_key_file_get_value (new_config, group, "xa.filter", NULL); @@ -14704,11 +14973,11 @@ flatpak_dir_modify_remote (FlatpakDir *self, if (!g_file_replace_contents (filter_copy, backup_data_copy, strlen (backup_data_copy), NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, &local_error)) - g_debug ("Failed to save backup copy of filter file %s: %s\n", filter_path, local_error->message); + g_info ("Failed to save backup copy of filter file %s: %s\n", filter_path, local_error->message); } else { - g_debug ("Failed to read filter %s file while making a backup copy: %s\n", filter_path, local_error->message); + g_info ("Failed to read filter %s file while making a backup copy: %s\n", filter_path, local_error->message); } } @@ -15031,14 +15300,14 @@ flatpak_dir_update_remote_configuration (FlatpakDir *self, if (!gpg_verify_summary || !gpg_verify) { - g_debug ("Ignoring automatic updates for system-helper remotes without gpg signatures"); + g_info ("Ignoring automatic updates for system-helper remotes without gpg signatures"); return TRUE; } if ((state->summary != NULL && state->summary_sig_bytes == NULL) || (state->index != NULL && state->index_sig_bytes == NULL)) { - g_debug ("Can't update remote configuration as user, no GPG signature"); + g_info ("Can't update remote configuration as user, no GPG signature"); return TRUE; } @@ -15175,9 +15444,9 @@ add_related (FlatpakDir *self, 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)); + g_info ("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; } @@ -15490,7 +15759,7 @@ flatpak_dir_find_remote_related (FlatpakDir *self, metadata_file = g_file_get_child (deploy_dir, "metadata"); if (!g_file_load_contents (metadata_file, cancellable, &metadata, NULL, NULL, NULL)) { - g_debug ("No metadata in local deploy"); + g_info ("No metadata in local deploy"); /* No metadata => no related, but no error */ } } @@ -15777,11 +16046,14 @@ flatpak_dir_find_local_related (FlatpakDir *self, if (deploy_data == NULL) return NULL; - metadata = g_file_get_child (deploy_dir, "metadata"); - if (!g_file_load_contents (metadata, cancellable, &metadata_contents, NULL, NULL, NULL)) + if (flatpak_deploy_data_get_extension_of (deploy_data) == NULL) { - g_debug ("No metadata in local deploy"); - /* No metadata => no related, but no error */ + metadata = g_file_get_child (deploy_dir, "metadata"); + if (!g_file_load_contents (metadata, cancellable, &metadata_contents, NULL, NULL, NULL)) + { + g_info ("No metadata in local deploy"); + /* No metadata => no related, but no error */ + } } } else @@ -15793,7 +16065,7 @@ flatpak_dir_find_local_related (FlatpakDir *self, g_autoptr(GVariant) commit_metadata = g_variant_get_child_value (commit_data, 0); g_variant_lookup (commit_metadata, "xa.metadata", "s", &metadata_contents); if (metadata_contents == NULL) - g_debug ("No xa.metadata in local commit %s ref %s", checksum, flatpak_decomposed_get_ref (ref)); + g_info ("No xa.metadata in local commit %s ref %s", checksum, flatpak_decomposed_get_ref (ref)); } } @@ -15819,7 +16091,7 @@ flatpak_dir_get_remote_auto_install_authenticator_ref (FlatpakDir *self, g_autoptr(GError) local_error = NULL; ref = flatpak_decomposed_new_from_parts (FLATPAK_KINDS_APP, authenticator_name, flatpak_get_arch (), "autoinstall", &local_error); if (ref == NULL) - g_debug ("Invalid authenticator ref: %s\n", local_error->message); + g_info ("Invalid authenticator ref: %s\n", local_error->message); } return g_steal_pointer (&ref); @@ -16243,8 +16515,8 @@ flatpak_dir_delete_mirror_refs (FlatpakDir *self, { 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); + g_info ("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; } diff --git a/common/flatpak-enum-types.c.template b/common/flatpak-enum-types.c.template index 5ce936c7..74c70869 100644 --- a/common/flatpak-enum-types.c.template +++ b/common/flatpak-enum-types.c.template @@ -8,7 +8,7 @@ /*** END file-header ***/ /*** BEGIN file-production ***/ -/* enumerations from "@filename@" */ +/* enumerations from "@basename@" */ /*** END file-production ***/ /*** BEGIN value-header ***/ diff --git a/common/flatpak-enum-types.h.template b/common/flatpak-enum-types.h.template index 5b67b9c0..9b343088 100644 --- a/common/flatpak-enum-types.h.template +++ b/common/flatpak-enum-types.h.template @@ -9,7 +9,7 @@ G_BEGIN_DECLS /*** BEGIN file-production ***/ -/* enumerations from "@filename@" */ +/* enumerations from "@basename@" */ /*** END file-production ***/ /*** BEGIN value-header ***/ diff --git a/common/flatpak-exports.c b/common/flatpak-exports.c index 6ac9dd46..15acc5b8 100644 --- a/common/flatpak-exports.c +++ b/common/flatpak-exports.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2014-2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -93,6 +93,40 @@ is_export_mode (int mode) || mode == FAKE_MODE_SYMLINK); } +static inline const char * +export_mode_to_verb (int mode) +{ + switch (mode) + { + case FAKE_MODE_DIR: + return "ensure existence of directory"; + + case FAKE_MODE_SYMLINK: + return "create symbolic link"; + + default: + break; + } + + switch ((FlatpakFilesystemMode) mode) + { + case FLATPAK_FILESYSTEM_MODE_READ_ONLY: + return "export read-only"; + + case FLATPAK_FILESYSTEM_MODE_CREATE: + return "create and export read/write"; + + case FLATPAK_FILESYSTEM_MODE_READ_WRITE: + return "export read/write"; + + case FLATPAK_FILESYSTEM_MODE_NONE: + return "replace with tmpfs"; + + default: + return "[use unknown/invalid mode?]"; + } +} + typedef struct { char *path; @@ -394,6 +428,8 @@ flatpak_exports_append_bwrap_args (FlatpakExports *exports, g_qsort_with_data (keys, n_keys, sizeof (char *), (GCompareDataFunc) flatpak_strcmp0_ptr, NULL); + g_debug ("Converting FlatpakExports to bwrap arguments..."); + for (l = eps; l != NULL; l = l->next) { ExportedPath *ep = l->data; @@ -403,7 +439,14 @@ flatpak_exports_append_bwrap_args (FlatpakExports *exports, if (ep->mode == FAKE_MODE_SYMLINK) { - if (!path_parent_is_mapped (keys, n_keys, exports->hash, path)) + g_debug ("\"%s\" is meant to be a symlink", path); + + if (path_parent_is_mapped (keys, n_keys, exports->hash, path)) + { + g_debug ("Not creating \"%s\" as symlink because its parent is " + "already mapped", path); + } + else { g_autofree char *resolved = flatpak_exports_resolve_link_in_host (exports, path, @@ -412,30 +455,60 @@ flatpak_exports_append_bwrap_args (FlatpakExports *exports, { g_autofree char *parent = g_path_get_dirname (path); g_autofree char *relative = make_relative (parent, resolved); + + g_debug ("Resolved \"%s\" to \"%s\" in host", path, resolved); + g_debug ("Creating \"%s\" -> \"%s\" in sandbox", path, relative); flatpak_bwrap_add_args (bwrap, "--symlink", relative, path, NULL); } + else + { + g_debug ("Unable to resolve \"%s\" in host, skipping", path); + } } } else if (ep->mode == FAKE_MODE_TMPFS) { + g_debug ("\"%s\" is meant to be a tmpfs or empty directory", path); + /* Mount a tmpfs to hide the subdirectory, but only if there is a pre-existing dir we can mount the path on. */ if (path_is_dir (exports, path)) { if (!path_parent_is_mapped (keys, n_keys, exports->hash, path)) /* If the parent is not mapped, it will be a tmpfs, no need to mount another one */ - flatpak_bwrap_add_args (bwrap, "--dir", path, NULL); + { + g_debug ("Parent of \"%s\" is not mapped, creating empty directory", path); + flatpak_bwrap_add_args (bwrap, "--dir", path, NULL); + } else - flatpak_bwrap_add_args (bwrap, "--tmpfs", path, NULL); + { + g_debug ("Parent of \"%s\" is mapped, creating tmpfs to shadow it", path); + flatpak_bwrap_add_args (bwrap, "--tmpfs", path, NULL); + } + } + else + { + g_debug ("Not a directory, skipping: \"%s\"", path); } } else if (ep->mode == FAKE_MODE_DIR) { + g_debug ("\"%s\" is meant to be a directory", path); + if (path_is_dir (exports, path)) - flatpak_bwrap_add_args (bwrap, "--dir", path, NULL); + { + g_debug ("Ensuring \"%s\" is created as a directory", path); + flatpak_bwrap_add_args (bwrap, "--dir", path, NULL); + } + else + { + g_debug ("Not a directory, skipping: \"%s\"", path); + } } else { + g_debug ("\"%s\" is meant to be shared (ro or rw) with the container", + path); flatpak_bwrap_add_args (bwrap, (ep->mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY) ? "--ro-bind" : "--bind", path, path, NULL); @@ -679,9 +752,29 @@ do_export_path (FlatpakExports *exports, ep->path = g_strdup (path); if (old_ep != NULL) - ep->mode = MAX (old_ep->mode, mode); + { + if (old_ep->mode < mode) + { + g_debug ("Increasing export mode from \"%s\" to \"%s\": %s", + export_mode_to_verb (old_ep->mode), + export_mode_to_verb (mode), + path); + ep->mode = mode; + } + else + { + g_debug ("Not changing export mode from \"%s\" to \"%s\": %s", + export_mode_to_verb (old_ep->mode), + export_mode_to_verb (mode), + path); + ep->mode = old_ep->mode; + } + } else - ep->mode = mode; + { + g_debug ("Will %s: %s", export_mode_to_verb (mode), path); + ep->mode = mode; + } g_hash_table_replace (exports->hash, ep->path, ep); } @@ -784,36 +877,53 @@ _exports_path_expose (FlatpakExports *exports, g_return_val_if_fail (is_export_mode (mode), FALSE); + g_debug ("Trying to %s: %s", export_mode_to_verb (mode), path); + if (level > 40) /* 40 is the current kernel ELOOP check */ { - g_debug ("Expose too deep, bail"); + g_info ("Expose too deep, bail"); return FALSE; } if (!g_path_is_absolute (path)) { - g_debug ("Not exposing relative path %s", path); + g_info ("Not exposing relative path %s", path); return FALSE; } /* Check if it exists at all */ o_path_fd = flatpak_exports_open_in_host (exports, path, O_PATH | O_NOFOLLOW); + if (o_path_fd == -1) - return FALSE; + { + g_info ("Unable to open path %s to %s: %s", + path, export_mode_to_verb (mode), g_strerror (errno)); + return FALSE; + } if (fstat (o_path_fd, &st) != 0) - return FALSE; + { + g_info ("Unable to get file type of %s: %s", path, g_strerror (errno)); + return FALSE; + } /* Don't expose weird things */ if (!(S_ISDIR (st.st_mode) || S_ISREG (st.st_mode) || S_ISLNK (st.st_mode) || S_ISSOCK (st.st_mode))) - return FALSE; + { + g_info ("%s has unsupported file type 0o%o", path, st.st_mode & S_IFMT); + return FALSE; + } /* O_PATH + fstatfs is the magic that we need to statfs without automounting the target */ if (fstatfs (o_path_fd, &stfs) != 0) - return FALSE; + { + g_info ("Unable to get filesystem information for %s: %s", + path, g_strerror (errno)); + return FALSE; + } if (stfs.f_type == AUTOFS_SUPER_MAGIC || (G_UNLIKELY (exports->test_flags & FLATPAK_EXPORTS_TEST_FLAGS_AUTOFS) && @@ -821,7 +931,7 @@ _exports_path_expose (FlatpakExports *exports, { if (!check_if_autofs_works (exports, path)) { - g_debug ("ignoring blocking autofs path %s", path); + g_info ("ignoring blocking autofs path %s", path); return FALSE; } } @@ -836,7 +946,7 @@ _exports_path_expose (FlatpakExports *exports, create the parents for them anyway */ if (flatpak_has_path_prefix (path, dont_export_in[i])) { - g_debug ("skipping export for path %s", path); + g_info ("skipping export for path %s in unsupported prefix", path); return FALSE; } } @@ -846,7 +956,7 @@ _exports_path_expose (FlatpakExports *exports, /* Same as /usr, but for the directories that get merged into /usr */ if (flatpak_has_path_prefix (path, flatpak_abs_usrmerged_dirs[i])) { - g_debug ("skipping export for path %s", path); + g_info ("skipping export for path %s in a /usr-merged directory", path); return FALSE; } } @@ -860,27 +970,49 @@ _exports_path_expose (FlatpakExports *exports, if (slash) *slash = 0; - if (path_is_symlink (exports, path) && !never_export_as_symlink (path)) + if (!path_is_symlink (exports, path)) + { + g_debug ("%s is not a symlink", path); + } + else if (never_export_as_symlink (path)) + { + g_debug ("%s is a symlink, but we avoid exporting it as such", path); + } + else { - g_autofree char *resolved = flatpak_exports_resolve_link_in_host (exports, path, NULL); + g_autoptr(GError) error = NULL; + g_autofree char *resolved = flatpak_exports_resolve_link_in_host (exports, path, &error); g_autofree char *new_target = NULL; if (resolved) { + g_debug ("%s is a symlink, resolved to %s", path, resolved); + if (slash) new_target = g_build_filename (resolved, slash + 1, NULL); else new_target = g_strdup (resolved); + g_debug ("Trying to export the target instead: %s", new_target); + if (_exports_path_expose (exports, mode, new_target, level + 1)) { do_export_path (exports, path, FAKE_MODE_SYMLINK); return TRUE; } - } - return FALSE; + g_debug ("Could not export target %s, so ignoring %s", + new_target, path); + return FALSE; + } + else + { + g_debug ("%s is a symlink but we were unable to resolve it: %s", + path, error->message); + return FALSE; + } } + if (slash) *slash = '/'; } diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c index aa11ebec..c19ef6bc 100644 --- a/common/flatpak-installation.c +++ b/common/flatpak-installation.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2015 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -320,11 +320,11 @@ flatpak_installation_new_system_with_id (const char *id, &local_error); if (installation == NULL) { - g_debug ("Error creating Flatpak installation: %s", local_error->message); + g_info ("Error creating Flatpak installation: %s", local_error->message); g_propagate_error (error, g_steal_pointer (&local_error)); } - g_debug ("Found Flatpak installation for '%s'", id); + g_info ("Found Flatpak installation for '%s'", id); return g_steal_pointer (&installation); } @@ -1013,7 +1013,7 @@ transaction_ready (FlatpakTransaction *transaction, if (type == FLATPAK_TRANSACTION_OPERATION_UNINSTALL) { const char *ref = flatpak_transaction_operation_get_ref (op); - g_debug ("Update transaction wants to uninstall %s", ref); + g_info ("Update transaction wants to uninstall %s", ref); continue; } @@ -1105,7 +1105,7 @@ flatpak_installation_list_installed_refs_for_update (FlatpakInstallation *self, if (g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_REMOTE_NOT_FOUND)) { - g_debug ("%s: Unable to update %s: %s", G_STRFUNC, ref, local_error->message); + g_info ("%s: Unable to update %s: %s", G_STRFUNC, ref, local_error->message); g_clear_error (&local_error); } else @@ -1159,7 +1159,7 @@ flatpak_installation_list_installed_refs_for_update (FlatpakInstallation *self, if (!g_hash_table_contains (installed_refs_for_update_set, op_ref)) { g_hash_table_add (installed_refs_for_update_set, (char *)op_ref); - g_debug ("%s: Installed ref %s needs update", G_STRFUNC, op_ref); + g_info ("%s: Installed ref %s needs update", G_STRFUNC, op_ref); g_ptr_array_add (installed_refs_for_update, g_object_ref (installed_ref)); } @@ -1177,7 +1177,7 @@ flatpak_installation_list_installed_refs_for_update (FlatpakInstallation *self, if (installed_ref != NULL) { g_hash_table_add (installed_refs_for_update_set, (char *)related_op_ref); - g_debug ("%s: Installed ref %s needs update", G_STRFUNC, related_op_ref); + g_info ("%s: Installed ref %s needs update", G_STRFUNC, related_op_ref); g_ptr_array_add (installed_refs_for_update, g_object_ref (installed_ref)); } @@ -1200,7 +1200,7 @@ flatpak_installation_list_installed_refs_for_update (FlatpakInstallation *self, if (!g_hash_table_contains (installed_refs_for_update_set, rebased_ref)) { g_hash_table_add (installed_refs_for_update_set, (char *)rebased_ref); - g_debug ("%s: Installed ref %s needs update", G_STRFUNC, rebased_ref); + g_info ("%s: Installed ref %s needs update", G_STRFUNC, rebased_ref); g_ptr_array_add (installed_refs_for_update, g_object_ref (installed_ref)); } diff --git a/common/flatpak-installed-ref.c b/common/flatpak-installed-ref.c index 02c60b67..c3f8048c 100644 --- a/common/flatpak-installed-ref.c +++ b/common/flatpak-installed-ref.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2015 Red Hat, Inc * * This program is free software; you can redistribute it and/or diff --git a/common/flatpak-instance.c b/common/flatpak-instance.c index 234a4b8b..3623043d 100644 --- a/common/flatpak-instance.c +++ b/common/flatpak-instance.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -322,7 +322,7 @@ get_instance_info (const char *dir) key_file = g_key_file_new (); if (!g_key_file_load_from_file (key_file, file, G_KEY_FILE_NONE, &error)) { - g_debug ("Failed to load instance info file '%s': %s", file, error->message); + g_info ("Failed to load instance info file '%s': %s", file, error->message); return NULL; } @@ -344,21 +344,21 @@ get_child_pid (const char *dir) if (!g_file_get_contents (file, &contents, &length, &error)) { - g_debug ("Failed to load bwrapinfo.json file '%s': %s", file, error->message); + g_info ("Failed to load bwrapinfo.json file '%s': %s", file, error->message); return 0; } parser = json_parser_new (); if (!json_parser_load_from_data (parser, contents, length, &error)) { - g_debug ("Failed to parse bwrapinfo.json file '%s': %s", file, error->message); + g_info ("Failed to parse bwrapinfo.json file '%s': %s", file, error->message); return 0; } node = json_parser_get_root (parser); if (!node) { - g_debug ("Failed to parse bwrapinfo.json file '%s': %s", file, "empty"); + g_info ("Failed to parse bwrapinfo.json file '%s': %s", file, "empty"); return 0; } @@ -378,7 +378,7 @@ get_pid (const char *dir) if (!g_file_get_contents (file, &contents, NULL, &error)) { - g_debug ("Failed to load pid file '%s': %s", file, error->message); + g_info ("Failed to load pid file '%s': %s", file, error->message); return 0; } @@ -730,7 +730,8 @@ flatpak_instance_allocate_id (char **host_dir_out, g_return_val_if_fail (lock_fd_out != NULL, NULL); g_return_val_if_fail (*lock_fd_out == -1, NULL); - g_mkdir_with_parents (base_dir, 0755); + if (g_mkdir_with_parents (base_dir, 0755) != 0) + return NULL; flatpak_instance_iterate_all_and_gc (NULL); @@ -766,7 +767,7 @@ flatpak_instance_allocate_id (char **host_dir_out, if (lock_fd != -1 && fcntl (lock_fd, F_SETLK, &l) == 0) { *lock_fd_out = glnx_steal_fd (&lock_fd); - g_debug ("Allocated instance id %s", instance_id); + g_info ("Allocated instance id %s", instance_id); *host_dir_out = g_steal_pointer (&instance_dir); return g_steal_pointer (&instance_id); } @@ -989,7 +990,7 @@ flatpak_instance_gc_per_app_dirs (const char *instance_id, if (statbuf.st_mtime + 3 >= time (NULL)) return glnx_throw (error, "lock file too recent, avoiding race condition"); - g_debug ("Cleaning up per-app-ID state for %s", app_id); + g_info ("Cleaning up per-app-ID state for %s", app_id); /* /dev/shm is offloaded onto the host's /dev/shm to get consistent * free space behaviour and make sure it's actually in RAM. It could @@ -1017,13 +1018,13 @@ flatpak_instance_gc_per_app_dirs (const char *instance_id, g_assert (g_str_has_prefix (path, "/dev/shm/")); if (unlinkat (per_app_dir_fd, "dev-shm", 0) != 0) - g_debug ("Unable to clean up %s/%s: %s", - per_app_dir, "dev-shm", g_strerror (errno)); + g_info ("Unable to clean up %s/%s: %s", + per_app_dir, "dev-shm", g_strerror (errno)); if (!glnx_shutil_rm_rf_at (AT_FDCWD, path, NULL, &local_error)) { - g_debug ("Unable to clean up %s: %s", - path, local_error->message); + g_info ("Unable to clean up %s: %s", + path, local_error->message); g_clear_error (&local_error); } } @@ -1034,9 +1035,9 @@ flatpak_instance_gc_per_app_dirs (const char *instance_id, } else { - g_debug ("%s/%s no longer points to the expected directory and " - "was removed: %s", - per_app_dir, "dev-shm", local_error->message); + g_info ("%s/%s no longer points to the expected directory and " + "was removed: %s", + per_app_dir, "dev-shm", local_error->message); g_clear_error (&local_error); } } @@ -1048,8 +1049,8 @@ flatpak_instance_gc_per_app_dirs (const char *instance_id, * and not a symlink. If it's a symlink, we'll just unlink it. */ if (!glnx_shutil_rm_rf_at (per_app_dir_fd, "tmp", NULL, &local_error)) { - g_debug ("Unable to clean up %s/tmp: %s", per_app_dir, - local_error->message); + g_info ("Unable to clean up %s/tmp: %s", per_app_dir, + local_error->message); g_clear_error (&local_error); } @@ -1103,10 +1104,10 @@ flatpak_instance_iterate_all_and_gc (GPtrArray *out_instances) g_autoptr(GError) local_error = NULL; /* The instance is not used, remove it */ - g_debug ("Cleaning up unused container id %s", dent->d_name); + g_info ("Cleaning up unused container id %s", dent->d_name); if (!flatpak_instance_gc_per_app_dirs (dent->d_name, &local_error)) - flatpak_debug2 ("Not cleaning up per-app dir: %s", local_error->message); + g_debug ("Not cleaning up per-app dir: %s", local_error->message); glnx_shutil_rm_rf_at (iter.fd, dent->d_name, NULL, NULL); continue; diff --git a/common/flatpak-json-oci.c b/common/flatpak-json-oci.c index 3b683d6d..4a429efc 100644 --- a/common/flatpak-json-oci.c +++ b/common/flatpak-json-oci.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright (C) 2015 Red Hat, Inc * * This file is free software; you can redistribute it and/or modify it diff --git a/common/flatpak-json.c b/common/flatpak-json.c index 4d684fac..dfabdbe9 100644 --- a/common/flatpak-json.c +++ b/common/flatpak-json.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright (C) 2015 Red Hat, Inc * * This file is free software; you can redistribute it and/or modify it diff --git a/common/flatpak-oci-registry-private.h b/common/flatpak-oci-registry-private.h index 66d69f08..83ec8467 100644 --- a/common/flatpak-oci-registry-private.h +++ b/common/flatpak-oci-registry-private.h @@ -160,24 +160,24 @@ FlatpakOciSignature *flatpak_oci_verify_signature (OstreeRepo *repo, GBytes *signature, GError **error); -gboolean flatpak_oci_index_ensure_cached (SoupSession *soup_session, - const char *uri, - GFile *index, - char **index_uri_out, - GCancellable *cancellable, - GError **error); +gboolean flatpak_oci_index_ensure_cached (FlatpakHttpSession *http_session, + const char *uri, + GFile *index, + char **index_uri_out, + GCancellable *cancellable, + GError **error); GVariant *flatpak_oci_index_make_summary (GFile *index, const char *index_uri, GCancellable *cancellable, GError **error); -GBytes *flatpak_oci_index_make_appstream (SoupSession *soup_session, - GFile *index, - const char *index_uri, - const char *arch, - int icons_dfd, - GCancellable *cancellable, - GError **error); +GBytes *flatpak_oci_index_make_appstream (FlatpakHttpSession *http_session, + GFile *index, + const char *index_uri, + const char *arch, + int icons_dfd, + GCancellable *cancellable, + GError **error); #endif /* __FLATPAK_OCI_REGISTRY_H__ */ diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c index 77b13901..1c87feee 100644 --- a/common/flatpak-oci-registry.c +++ b/common/flatpak-oci-registry.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2016 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -27,10 +27,10 @@ #include "libglnx.h" #include <gpgme.h> -#include <libsoup/soup.h> #include "flatpak-oci-registry-private.h" #include "flatpak-utils-base-private.h" #include "flatpak-utils-private.h" +#include "flatpak-uri-private.h" #include "flatpak-dir-private.h" #include "flatpak-zstd-decompressor-private.h" @@ -67,8 +67,8 @@ struct FlatpakOciRegistry int dfd; /* Remote repos */ - SoupSession *soup_session; - SoupURI *base_uri; + FlatpakHttpSession *http_session; + GUri *base_uri; }; typedef struct @@ -88,6 +88,20 @@ G_DEFINE_TYPE_WITH_CODE (FlatpakOciRegistry, flatpak_oci_registry, G_TYPE_OBJECT G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, flatpak_oci_registry_initable_iface_init)) +static gchar * +parse_relative_uri (GUri *base_uri, + const char *subpath, + GError **error) +{ + g_autoptr(GUri) uri = NULL; + + uri = g_uri_parse_relative (base_uri, subpath, FLATPAK_HTTP_URI_FLAGS | G_URI_FLAGS_PARSE_RELAXED, error); + if (uri == NULL) + return NULL; + + return g_uri_to_string_partial (uri, G_URI_HIDE_PASSWORD); +} + static void flatpak_oci_registry_finalize (GObject *object) { @@ -96,8 +110,8 @@ flatpak_oci_registry_finalize (GObject *object) if (self->dfd != -1) close (self->dfd); - g_clear_object (&self->soup_session); - g_clear_pointer (&self->base_uri, soup_uri_free); + g_clear_pointer (&self->http_session, flatpak_http_session_free); + g_clear_pointer (&self->base_uri, g_uri_unref); g_free (self->uri); g_free (self->token); @@ -312,7 +326,7 @@ local_load_file (int dfd, /* We just support the first http uri for now */ static char * -choose_alt_uri (SoupURI *base_uri, +choose_alt_uri (GUri *base_uri, const char **alt_uris) { int i; @@ -331,8 +345,8 @@ choose_alt_uri (SoupURI *base_uri, } static GBytes * -remote_load_file (SoupSession *soup_session, - SoupURI *base, +remote_load_file (FlatpakHttpSession *http_session, + GUri *base, const char *subpath, const char **alt_uris, const char *token, @@ -340,25 +354,18 @@ remote_load_file (SoupSession *soup_session, GCancellable *cancellable, GError **error) { - g_autoptr(SoupURI) uri = NULL; g_autoptr(GBytes) bytes = NULL; g_autofree char *uri_s = NULL; uri_s = choose_alt_uri (base, alt_uris); if (uri_s == NULL) { - uri = soup_uri_new_with_base (base, subpath); - if (uri == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - "Invalid relative url %s", subpath); - return NULL; - } - - uri_s = soup_uri_to_string (uri, FALSE); + uri_s = parse_relative_uri (base, subpath, error); + if (uri_s == NULL) + return NULL; } - bytes = flatpak_load_uri (soup_session, + bytes = flatpak_load_uri (http_session, uri_s, FLATPAK_HTTP_FLAGS_ACCEPT_OCI, token, NULL, NULL, out_content_type, @@ -380,7 +387,7 @@ flatpak_oci_registry_load_file (FlatpakOciRegistry *self, if (self->dfd != -1) return local_load_file (self->dfd, subpath, cancellable, error); else - return remote_load_file (self->soup_session, self->base_uri, subpath, alt_uris, self->token, out_content_type, cancellable, error); + return remote_load_file (self->http_session, self->base_uri, subpath, alt_uris, self->token, out_content_type, cancellable, error); } static JsonNode * @@ -532,7 +539,7 @@ flatpak_oci_registry_ensure_remote (FlatpakOciRegistry *self, GCancellable *cancellable, GError **error) { - g_autoptr(SoupURI) baseuri = NULL; + g_autoptr(GUri) baseuri = NULL; if (for_write) { @@ -541,8 +548,8 @@ flatpak_oci_registry_ensure_remote (FlatpakOciRegistry *self, return FALSE; } - self->soup_session = flatpak_create_soup_session (PACKAGE_STRING); - baseuri = soup_uri_new (self->uri); + self->http_session = flatpak_create_http_session (PACKAGE_STRING); + baseuri = g_uri_parse (self->uri, FLATPAK_HTTP_URI_FLAGS | G_URI_FLAGS_PARSE_RELAXED, NULL); if (baseuri == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, @@ -783,7 +790,6 @@ flatpak_oci_registry_download_blob (FlatpakOciRegistry *self, } else { - g_autoptr(SoupURI) uri = NULL; g_autofree char *uri_s = NULL; g_autofree char *checksum = NULL; g_autofree char *tmpfile_name = g_strdup_printf ("oci-layer-XXXXXX"); @@ -794,15 +800,9 @@ flatpak_oci_registry_download_blob (FlatpakOciRegistry *self, uri_s = choose_alt_uri (self->base_uri, alt_uris); if (uri_s == NULL) { - uri = soup_uri_new_with_base (self->base_uri, subpath); - if (uri == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - "Invalid relative url %s", subpath); - return -1; - } - - uri_s = soup_uri_to_string (uri, FALSE); + uri_s = parse_relative_uri (self->base_uri, subpath, error); + if (uri_s == NULL) + return -1; } @@ -816,7 +816,7 @@ flatpak_oci_registry_download_blob (FlatpakOciRegistry *self, if (fd == -1) return -1; - if (!flatpak_download_http_uri (self->soup_session, uri_s, + if (!flatpak_download_http_uri (self->http_session, uri_s, FLATPAK_HTTP_FLAGS_ACCEPT_OCI, out_stream, self->token, @@ -902,21 +902,13 @@ flatpak_oci_registry_mirror_blob (FlatpakOciRegistry *self, } else { - g_autoptr(SoupURI) uri = NULL; - g_autofree char *uri_s = NULL; - - uri = soup_uri_new_with_base (source_registry->base_uri, src_subpath); - if (uri == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - "Invalid relative url %s", src_subpath); - return FALSE; - } + g_autofree char *uri_s = parse_relative_uri (source_registry->base_uri, src_subpath, error); + if (uri_s == NULL) + return FALSE; out_stream = g_unix_output_stream_new (tmpf.fd, FALSE); - uri_s = soup_uri_to_string (uri, FALSE); - if (!flatpak_download_http_uri (source_registry->soup_session, uri_s, + if (!flatpak_download_http_uri (source_registry->http_session, uri_s, FLATPAK_HTTP_FLAGS_ACCEPT_OCI, out_stream, self->token, progress_cb, user_data, @@ -967,6 +959,17 @@ object_get_string_member_with_default (JsonNode *json, return json_node_get_string (node); } +static const char * +object_find_error_string (JsonNode *json) +{ + const char *error_detail = NULL; + error_detail = object_get_string_member_with_default (json, "details", NULL); + if (error_detail == NULL) + error_detail = object_get_string_member_with_default (json, "message", NULL); + if (error_detail == NULL) + error_detail = object_get_string_member_with_default (json, "error", NULL); + return error_detail; +} static char * get_token_for_www_auth (FlatpakOciRegistry *self, @@ -976,15 +979,16 @@ get_token_for_www_auth (FlatpakOciRegistry *self, GCancellable *cancellable, GError **error) { - g_autoptr(GInputStream) auth_stream = NULL; - g_autoptr(SoupMessage) auth_msg = NULL; g_autoptr(GHashTable) params = NULL; - g_autoptr(GHashTable) args = NULL; + g_autoptr(GString) args = NULL; const char *realm, *service, *scope, *token, *body_data; g_autofree char *default_scope = NULL; - g_autoptr(SoupURI) auth_uri = NULL; + g_autoptr(GUri) auth_uri = NULL; + g_autofree char *auth_uri_s = NULL; g_autoptr(GBytes) body = NULL; g_autoptr(JsonNode) json = NULL; + GUri *tmp_uri; + int http_status; if (g_ascii_strncasecmp (www_authenticate, "Bearer ", strlen ("Bearer ")) != 0) { @@ -992,7 +996,7 @@ get_token_for_www_auth (FlatpakOciRegistry *self, return NULL; } - params = soup_header_parse_param_list (www_authenticate + strlen ("Bearer ")); + params = flatpak_parse_http_header_param_list (www_authenticate + strlen ("Bearer ")); realm = g_hash_table_lookup (params, "realm"); if (realm == NULL) @@ -1001,59 +1005,76 @@ get_token_for_www_auth (FlatpakOciRegistry *self, return NULL; } - auth_uri = soup_uri_new (realm); + auth_uri = g_uri_parse (realm, FLATPAK_HTTP_URI_FLAGS | G_URI_FLAGS_PARSE_RELAXED, NULL); if (auth_uri == NULL) { flatpak_fail (error, _("Invalid realm in authentication request")); return NULL; } - args = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + args = g_string_new (NULL); + service = g_hash_table_lookup (params, "service"); if (service) - g_hash_table_insert (args, "service", (char *)service); + flatpak_uri_encode_query_arg (args, "service", (char *)service); scope = g_hash_table_lookup (params, "scope"); if (scope == NULL) scope = default_scope = g_strdup_printf("repository:%s:pull", repository); - g_hash_table_insert (args, "scope", (char *)scope); - - soup_uri_set_query_from_form (auth_uri, args); - - auth_msg = soup_message_new_from_uri ("GET", auth_uri); - - if (auth) - { - g_autofree char *basic_auth = g_strdup_printf ("Basic %s", auth); - soup_message_headers_replace (auth_msg->request_headers, "Authorization", basic_auth); - } - - auth_stream = soup_session_send (self->soup_session, auth_msg, NULL, error); - if (auth_stream == NULL) - return NULL; - body = flatpak_read_stream (auth_stream, TRUE, error); + flatpak_uri_encode_query_arg (args, "scope", (char *)scope); + + tmp_uri = g_uri_build (g_uri_get_flags (auth_uri) | G_URI_FLAGS_ENCODED_QUERY, + g_uri_get_scheme (auth_uri), + g_uri_get_userinfo (auth_uri), + g_uri_get_host (auth_uri), + g_uri_get_port (auth_uri), + g_uri_get_path (auth_uri), + args->str, + g_uri_get_fragment (auth_uri)); + g_uri_unref (auth_uri); + auth_uri = tmp_uri; + auth_uri_s = g_uri_to_string_partial (auth_uri, G_URI_HIDE_PASSWORD); + + body = flatpak_load_uri_full (self->http_session, + auth_uri_s, + FLATPAK_HTTP_FLAGS_NOCHECK_STATUS, + auth, NULL, + NULL, NULL, + &http_status, NULL, NULL, + cancellable, error); if (body == NULL) return NULL; body_data = (char *)g_bytes_get_data (body, NULL); - if (!SOUP_STATUS_IS_SUCCESSFUL (auth_msg->status_code)) + if (http_status < 200 || http_status >= 300) { const char *error_detail = NULL; json = json_from_string (body_data, NULL); if (json) { - error_detail = object_get_string_member_with_default (json, "details", NULL); - if (error_detail == NULL) - error_detail = object_get_string_member_with_default (json, "message", NULL); - if (error_detail == NULL) - error_detail = object_get_string_member_with_default (json, "error", NULL); + error_detail = object_find_error_string (json); + if (error_detail == NULL && JSON_NODE_HOLDS_OBJECT(json)) + { + JsonNode *errors = json_object_get_member (json_node_get_object (json), "errors"); + if (errors && JSON_NODE_HOLDS_ARRAY (errors)) + { + JsonArray *array = json_node_get_array (errors); + for (int i = 0; i < json_array_get_length (array); i++) + { + error_detail = object_find_error_string (json_array_get_element (array, i)); + if (error_detail != 0) + break; + } + } + } } + if (error_detail == NULL) - g_debug ("Unhandled error body format: %s", body_data); + g_info ("Unhandled error body format: %s", body_data); - if (auth_msg->status_code == SOUP_STATUS_UNAUTHORIZED) + if (http_status == 401 /* UNAUTHORIZED */) { if (error_detail) flatpak_fail_error (error, FLATPAK_ERROR_NOT_AUTHORIZED, _("Authorization failed: %s"), error_detail); @@ -1062,7 +1083,7 @@ get_token_for_www_auth (FlatpakOciRegistry *self, return NULL; } - flatpak_fail (error, _("Unexpected response status %d when requesting token: %s"), auth_msg->status_code, (char *)g_bytes_get_data (body, NULL)); + flatpak_fail (error, _("Unexpected response status %d when requesting token: %s"), http_status, (char *)g_bytes_get_data (body, NULL)); return NULL; } @@ -1089,11 +1110,11 @@ flatpak_oci_registry_get_token (FlatpakOciRegistry *self, GError **error) { g_autofree char *subpath = NULL; - g_autoptr(SoupURI) uri = NULL; - g_autoptr(GInputStream) stream = NULL; - g_autoptr(SoupMessage) msg = NULL; + g_autofree char *uri_s = NULL; g_autofree char *www_authenticate = NULL; g_autofree char *token = NULL; + g_autoptr(GBytes) body = NULL; + int http_status; g_assert (self->valid); @@ -1104,38 +1125,32 @@ flatpak_oci_registry_get_token (FlatpakOciRegistry *self, if (self->dfd != -1) return g_strdup (""); // No tokens for local repos - uri = soup_uri_new_with_base (self->base_uri, subpath); - if (uri == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - "Invalid relative url %s", subpath); - return NULL; - } - - msg = soup_message_new_from_uri ("HEAD", uri); - - soup_message_headers_replace (msg->request_headers, "Accept", - FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFEST ", " FLATPAK_DOCKER_MEDIA_TYPE_IMAGE_MANIFEST2); + uri_s = parse_relative_uri (self->base_uri, subpath, error); + if (uri_s == NULL) + return NULL; - stream = soup_session_send (self->soup_session, msg, NULL, error); - if (stream == NULL) + body = flatpak_load_uri_full (self->http_session, uri_s, + FLATPAK_HTTP_FLAGS_ACCEPT_OCI | FLATPAK_HTTP_FLAGS_HEAD | FLATPAK_HTTP_FLAGS_NOCHECK_STATUS, + NULL, NULL, + NULL, NULL, + &http_status, NULL, &www_authenticate, + cancellable, error); + if (body == NULL) return NULL; - if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) - { - return g_strdup (""); - } - else if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) + if (http_status >= 200 && http_status < 300) + return g_strdup (""); + + if (http_status != 401 /* UNAUTHORIZED */) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unexpected response status %d from repo", msg->status_code); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unexpected response status %d from repo", http_status); return NULL; } /* Need www-authenticated header */ - www_authenticate = g_strdup (soup_message_headers_get_one (msg->response_headers, "WWW-Authenticate")); if (www_authenticate == NULL) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Now WWW-Authenticate header from repo"); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No WWW-Authenticate header from repo"); return NULL; } @@ -2044,14 +2059,15 @@ flatpak_oci_registry_find_delta_manifest (FlatpakOciRegistry *registry, if (delta_manifest_url != NULL) { - g_autoptr(SoupURI) soup_uri = soup_uri_new_with_base (registry->base_uri, delta_manifest_url); - g_autofree char *uri_s = soup_uri_to_string (soup_uri, FALSE); + g_autoptr(GBytes) bytes = NULL; + g_autofree char *uri_s = parse_relative_uri (registry->base_uri, delta_manifest_url, NULL); - g_autoptr(GBytes) bytes = flatpak_load_uri (registry->soup_session, - uri_s, FLATPAK_HTTP_FLAGS_ACCEPT_OCI, - registry->token, - NULL, NULL, NULL, - cancellable, NULL); + if (uri_s != NULL) + bytes = flatpak_load_uri (registry->http_session, + uri_s, FLATPAK_HTTP_FLAGS_ACCEPT_OCI, + registry->token, + NULL, NULL, NULL, + cancellable, NULL); if (bytes != NULL) { g_autoptr(FlatpakOciVersioned) versioned = @@ -2711,22 +2727,24 @@ compare_image_by_ref (ImageInfo *a, } gboolean -flatpak_oci_index_ensure_cached (SoupSession *soup_session, - const char *uri, - GFile *index, - char **index_uri_out, - GCancellable *cancellable, - GError **error) +flatpak_oci_index_ensure_cached (FlatpakHttpSession *http_session, + const char *uri, + GFile *index, + char **index_uri_out, + GCancellable *cancellable, + GError **error) { g_autofree char *index_path = g_file_get_path (index); - g_autoptr(SoupURI) base_uri = NULL; - g_autoptr(SoupURI) query_uri = NULL; + g_autoptr(GUri) base_uri = NULL; + g_autoptr(GUri) query_uri = NULL; g_autofree char *query_uri_s = NULL; + g_autoptr(GString) query = NULL; g_autoptr(GString) path = NULL; g_autofree char *tag = NULL; const char *oci_arch = NULL; gboolean success = FALSE; g_autoptr(GError) local_error = NULL; + GUri *tmp_uri; if (!g_str_has_prefix (uri, "oci+http:") && !g_str_has_prefix (uri, "oci+https:")) { @@ -2735,7 +2753,7 @@ flatpak_oci_index_ensure_cached (SoupSession *soup_session, return FALSE; } - base_uri = soup_uri_new (uri + 4); + base_uri = g_uri_parse (uri + 4, FLATPAK_HTTP_URI_FLAGS | G_URI_FLAGS_PARSE_RELAXED, NULL); if (base_uri == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, @@ -2743,7 +2761,7 @@ flatpak_oci_index_ensure_cached (SoupSession *soup_session, return FALSE; } - path = g_string_new (soup_uri_get_path (base_uri)); + path = g_string_new (g_uri_get_path (base_uri)); /* Append /index/static or /static to the path. */ @@ -2755,33 +2773,61 @@ flatpak_oci_index_ensure_cached (SoupSession *soup_session, g_string_append (path, "static"); - soup_uri_set_path (base_uri, path->str); + /* Replace path */ + tmp_uri = g_uri_build (g_uri_get_flags (base_uri), + g_uri_get_scheme (base_uri), + g_uri_get_userinfo (base_uri), + g_uri_get_host (base_uri), + g_uri_get_port (base_uri), + path->str, + g_uri_get_query (base_uri), + g_uri_get_fragment (base_uri)); + g_uri_unref (base_uri); + base_uri = tmp_uri; /* The fragment of the URI defines a tag to look for; if absent * or empty, we use 'latest' */ - tag = g_strdup (soup_uri_get_fragment (base_uri)); + tag = g_strdup (g_uri_get_fragment (base_uri)); if (tag == NULL || tag[0] == '\0') { g_clear_pointer (&tag, g_free); tag = g_strdup ("latest"); } - soup_uri_set_fragment (base_uri, NULL); - - query_uri = soup_uri_copy (base_uri); + /* Remove fragment */ + tmp_uri = g_uri_build (g_uri_get_flags (base_uri), + g_uri_get_scheme (base_uri), + g_uri_get_userinfo (base_uri), + g_uri_get_host (base_uri), + g_uri_get_port (base_uri), + g_uri_get_path (base_uri), + g_uri_get_query (base_uri), + NULL); + g_uri_unref (base_uri); + base_uri = tmp_uri; oci_arch = flatpak_arch_to_oci_arch (flatpak_get_arch ()); - soup_uri_set_query_from_fields (query_uri, - "label:org.flatpak.ref:exists", "1", - "architecture", oci_arch, - "os", "linux", - "tag", tag, - NULL); - query_uri_s = soup_uri_to_string (query_uri, FALSE); - success = flatpak_cache_http_uri (soup_session, + query = g_string_new (NULL); + flatpak_uri_encode_query_arg (query, "label:org.flatpak.ref:exists", "1"); + flatpak_uri_encode_query_arg (query, "architecture", oci_arch); + flatpak_uri_encode_query_arg (query, "os", "linux"); + flatpak_uri_encode_query_arg (query, "tag", tag); + + query_uri = g_uri_build (g_uri_get_flags (base_uri) | G_URI_FLAGS_ENCODED_QUERY, + g_uri_get_scheme (base_uri), + g_uri_get_userinfo (base_uri), + g_uri_get_host (base_uri), + g_uri_get_port (base_uri), + g_uri_get_path (base_uri), + query->str, + g_uri_get_fragment (base_uri)); + + query_uri_s = g_uri_to_string_partial (query_uri, G_URI_HIDE_PASSWORD); + + success = flatpak_cache_http_uri (http_session, query_uri_s, FLATPAK_HTTP_FLAGS_STORE_COMPRESSED, AT_FDCWD, index_path, @@ -2792,7 +2838,7 @@ flatpak_oci_index_ensure_cached (SoupSession *soup_session, g_error_matches (local_error, FLATPAK_HTTP_ERROR, FLATPAK_HTTP_ERROR_NOT_CHANGED)) { if (index_uri_out) - *index_uri_out = soup_uri_to_string (base_uri, FALSE); + *index_uri_out = g_uri_to_string_partial (base_uri, G_URI_HIDE_PASSWORD); } else { @@ -2857,7 +2903,6 @@ flatpak_oci_index_make_summary (GFile *index, GError **error) { g_autoptr(FlatpakOciIndexResponse) response = NULL; - g_autoptr(SoupURI) registry_uri = NULL; g_autofree char *registry_uri_s = NULL; int i; g_autoptr(GArray) images = g_array_new (FALSE, TRUE, sizeof (ImageInfo)); @@ -2867,15 +2912,16 @@ flatpak_oci_index_make_summary (GFile *index, g_autoptr(GVariantBuilder) summary_builder = NULL; g_autoptr(GVariant) summary = NULL; g_autoptr(GVariantBuilder) ref_data_builder = NULL; - g_autoptr(SoupURI) soup_uri = NULL; + g_autoptr(GUri) uri = NULL; response = load_oci_index (index, cancellable, error); if (!response) return NULL; - soup_uri = soup_uri_new (index_uri); - registry_uri = soup_uri_new_with_base (soup_uri, response->registry); - registry_uri_s = soup_uri_to_string (registry_uri, FALSE); + uri = g_uri_parse (index_uri, FLATPAK_HTTP_URI_FLAGS | G_URI_FLAGS_PARSE_RELAXED, NULL); + registry_uri_s = parse_relative_uri (uri, response->registry, error); + if (registry_uri_s == NULL) + return NULL; for (i = 0; response->results != NULL && response->results[i] != NULL; i++) { @@ -2939,7 +2985,7 @@ flatpak_oci_index_make_summary (GFile *index, if (!g_str_has_prefix (image->digest, "sha256:")) { - g_debug ("Ignoring digest type %s", image->digest); + g_info ("Ignoring digest type %s", image->digest); continue; } @@ -3017,15 +3063,15 @@ flatpak_oci_index_make_summary (GFile *index, } static gboolean -add_icon_image (SoupSession *soup_session, - const char *index_uri, - int icons_dfd, - GHashTable *used_icons, - const char *subdir, - const char *id, - const char *icon_data, - GCancellable *cancellable, - GError **error) +add_icon_image (FlatpakHttpSession *http_session, + const char *index_uri, + int icons_dfd, + GHashTable *used_icons, + const char *subdir, + const char *id, + const char *icon_data, + GCancellable *cancellable, + GError **error) { g_autofree char *icon_name = g_strconcat (id, ".png", NULL); g_autofree char *icon_path = g_build_filename (subdir, icon_name, NULL); @@ -3061,12 +3107,15 @@ add_icon_image (SoupSession *soup_session, } else { - g_autoptr(SoupURI) base_uri = soup_uri_new (index_uri); - g_autoptr(SoupURI) icon_uri = soup_uri_new_with_base (base_uri, icon_data); - g_autofree char *icon_uri_s = soup_uri_to_string (icon_uri, FALSE); + g_autoptr(GUri) base_uri = g_uri_parse (index_uri, FLATPAK_HTTP_URI_FLAGS | G_URI_FLAGS_PARSE_RELAXED, NULL); + g_autofree char *icon_uri_s = NULL; g_autoptr(GError) local_error = NULL; - if (!flatpak_cache_http_uri (soup_session, icon_uri_s, + icon_uri_s = parse_relative_uri (base_uri, icon_data, error); + if (icon_uri_s == NULL) + return FALSE; + + if (!flatpak_cache_http_uri (http_session, icon_uri_s, 0 /* flags */, icons_dfd, icon_path, NULL, NULL, @@ -3084,7 +3133,7 @@ add_icon_image (SoupSession *soup_session, } static void -add_image_to_appstream (SoupSession *soup_session, +add_image_to_appstream (FlatpakHttpSession *http_session, const char *index_uri, FlatpakXml *appstream_root, int icons_dfd, @@ -3175,7 +3224,7 @@ add_image_to_appstream (SoupSession *soup_session, const char *icon_data = get_image_metadata (image, icon_sizes[i].label); if (icon_data) { - if (!add_icon_image (soup_session, + if (!add_icon_image (http_session, index_uri, icons_dfd, used_icons, @@ -3260,13 +3309,13 @@ clean_unused_icons (int icons_dfd, } GBytes * -flatpak_oci_index_make_appstream (SoupSession *soup_session, - GFile *index, - const char *index_uri, - const char *arch, - int icons_dfd, - GCancellable *cancellable, - GError **error) +flatpak_oci_index_make_appstream (FlatpakHttpSession *http_session, + GFile *index, + const char *index_uri, + const char *arch, + int icons_dfd, + GCancellable *cancellable, + GError **error) { g_autoptr(FlatpakOciIndexResponse) response = NULL; g_autoptr(FlatpakXml) appstream_root = NULL; @@ -3294,7 +3343,7 @@ flatpak_oci_index_make_appstream (SoupSession *soup_session, { FlatpakOciIndexImage *image = r->images[j]; if (g_strcmp0 (image->architecture, oci_arch) == 0) - add_image_to_appstream (soup_session, + add_image_to_appstream (http_session, index_uri, appstream_root, icons_dfd, used_icons, r, image, @@ -3310,7 +3359,7 @@ flatpak_oci_index_make_appstream (SoupSession *soup_session, { FlatpakOciIndexImage *image = list->images[k]; if (g_strcmp0 (image->architecture, oci_arch) == 0) - add_image_to_appstream (soup_session, + add_image_to_appstream (http_session, index_uri, appstream_root, icons_dfd, used_icons, r, image, diff --git a/common/flatpak-parental-controls.c b/common/flatpak-parental-controls.c index c7815678..5b9d0c4f 100644 --- a/common/flatpak-parental-controls.c +++ b/common/flatpak-parental-controls.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2018 Endless Mobile, Inc. * * This program is free software; you can redistribute it and/or @@ -129,10 +129,10 @@ flatpak_oars_check_rating (GHashTable *content_rating, (rating_value != MCT_APP_FILTER_OARS_VALUE_UNKNOWN && filter_value == MCT_APP_FILTER_OARS_VALUE_UNKNOWN)) { - g_debug ("%s: Comparing rating ‘%s’: app has ‘%s’ but policy has ‘%s’ unknown: OARS check failed", - G_STRFUNC, oars_sections[i], - app_filter_oars_value_to_string (rating_value), - app_filter_oars_value_to_string (filter_value)); + g_info ("%s: Comparing rating ‘%s’: app has ‘%s’ but policy has ‘%s’ unknown: OARS check failed", + G_STRFUNC, oars_sections[i], + app_filter_oars_value_to_string (rating_value), + app_filter_oars_value_to_string (filter_value)); return FALSE; } } diff --git a/common/flatpak-progress.c b/common/flatpak-progress.c index 1e8842ae..1ca4b046 100644 --- a/common/flatpak-progress.c +++ b/common/flatpak-progress.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2019 Endless Mobile, Inc * * This program is free software; you can redistribute it and/or @@ -314,7 +314,7 @@ out: if (new_progress > 100) { if (!self->reported_overflow) - g_debug ("Unexpectedly got > 100%% progress, limiting"); + g_info ("Unexpectedly got > 100%% progress, limiting"); self->reported_overflow = TRUE; new_progress = 100; } diff --git a/common/flatpak-prune.c b/common/flatpak-prune.c index 78e397fd..0e23609f 100644 --- a/common/flatpak-prune.c +++ b/common/flatpak-prune.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2021 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -517,7 +517,7 @@ traverse_reachable_refs_unlocked (OstreeRepo *repo, if (object_name_bag_contains (reachable, &commit_name)) continue; - flatpak_debug2 ("Finding objects to keep for commit %s", checksum); + g_debug ("Finding objects to keep for commit %s", checksum); if (!load_extra_commitmeta (repo, checksum, &extra_commitmeta, cancellable, error)) return FALSE; @@ -608,8 +608,8 @@ prune_loose_object (OtPruneData *data, { guint64 storage_size = 0; - flatpak_debug2 ("Pruning unneeded object %s.%s", checksum, - ostree_object_type_to_string (objtype)); + g_debug ("Pruning unneeded object %s.%s", checksum, + ostree_object_type_to_string (objtype)); if (!ostree_repo_query_object_storage_size (data->repo, objtype, checksum, &storage_size, cancellable, error)) @@ -767,14 +767,14 @@ flatpak_repo_prune (OstreeRepo *repo, return FALSE; timer = g_timer_new (); - g_debug ("Finding reachable objects, unlocked (depth=%d)", depth); + g_info ("Finding reachable objects, unlocked (depth=%d)", depth); g_timer_start (timer); if (!traverse_reachable_refs_unlocked (repo, depth, reachable, cancellable, error)) return FALSE; g_timer_stop (timer); - g_debug ("Elapsed time: %.1f sec", g_timer_elapsed (timer, NULL)); + g_info ("Elapsed time: %.1f sec", g_timer_elapsed (timer, NULL)); } { @@ -785,7 +785,7 @@ flatpak_repo_prune (OstreeRepo *repo, return FALSE; timer = g_timer_new (); - g_debug ("Finding reachable objects, locked (depth=%d)", depth); + g_info ("Finding reachable objects, locked (depth=%d)", depth); g_timer_start (timer); if (!traverse_reachable_refs_unlocked (repo, depth, reachable, cancellable, error)) @@ -796,29 +796,29 @@ flatpak_repo_prune (OstreeRepo *repo, data.dont_prune = dry_run; g_timer_stop (timer); - g_debug ("Elapsed time: %.1f sec", g_timer_elapsed (timer, NULL)); + g_info ("Elapsed time: %.1f sec", g_timer_elapsed (timer, NULL)); - g_debug ("Pruning unreachable objects"); + g_info ("Pruning unreachable objects"); g_timer_start (timer); if (!prune_unreachable_loose_objects (repo, &data, cancellable, error)) return FALSE; g_timer_stop (timer); - g_debug ("Elapsed time: %.1f sec", g_timer_elapsed (timer, NULL)); + g_info ("Elapsed time: %.1f sec", g_timer_elapsed (timer, NULL)); } /* Prune static deltas outside lock to avoid conflict with its exclusive lock */ if (!dry_run) { - g_debug ("Pruning static deltas"); + g_info ("Pruning static deltas"); g_timer_start (timer); if (!ostree_repo_prune_static_deltas (repo, NULL, cancellable, error)) return FALSE; g_timer_stop (timer); - g_debug ("Elapsed time: %.1f sec", g_timer_elapsed (timer, NULL)); + g_info ("Elapsed time: %.1f sec", g_timer_elapsed (timer, NULL)); } *out_objects_total = data.n_reachable + data.n_unreachable; diff --git a/common/flatpak-ref-utils.c b/common/flatpak-ref-utils.c index 37abafb3..45b1405d 100644 --- a/common/flatpak-ref-utils.c +++ b/common/flatpak-ref-utils.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2014-2020 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -94,8 +94,6 @@ find_last_char (const char *str, gsize len, int c) * 2) DBus names require only two elements * * Returns: %TRUE if valid, %FALSE otherwise. - * - * Since: 2.26 */ gboolean flatpak_is_valid_name (const char *string, @@ -362,8 +360,6 @@ is_valid_branch_character (gint c) * Branch names must contain at least one character. * * Returns: %TRUE if valid, %FALSE otherwise. - * - * Since: 2.26 */ gboolean flatpak_is_valid_branch (const char *string, diff --git a/common/flatpak-ref.c b/common/flatpak-ref.c index 84773548..1c7fda7b 100644 --- a/common/flatpak-ref.c +++ b/common/flatpak-ref.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2015 Red Hat, Inc * * This program is free software; you can redistribute it and/or diff --git a/common/flatpak-related-ref.c b/common/flatpak-related-ref.c index ec9cb491..40764640 100644 --- a/common/flatpak-related-ref.c +++ b/common/flatpak-related-ref.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2015 Red Hat, Inc * * This program is free software; you can redistribute it and/or diff --git a/common/flatpak-remote-ref.c b/common/flatpak-remote-ref.c index 332d28d0..1868c725 100644 --- a/common/flatpak-remote-ref.c +++ b/common/flatpak-remote-ref.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2015 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -341,7 +341,7 @@ flatpak_remote_ref_new (FlatpakDecomposed *decomposed, &download_size, &installed_size, &metadata, NULL)) { - g_debug ("Can't find metadata for ref %s", flatpak_decomposed_get_ref (decomposed)); + g_info ("Can't find metadata for ref %s", flatpak_decomposed_get_ref (decomposed)); } if (metadata) diff --git a/common/flatpak-remote.c b/common/flatpak-remote.c index 42afcc06..c13d6c29 100644 --- a/common/flatpak-remote.c +++ b/common/flatpak-remote.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2015 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -120,6 +120,11 @@ flatpak_remote_finalize (GObject *object) g_free (priv->local_title); g_free (priv->local_default_branch); g_free (priv->local_main_ref); + g_free (priv->local_filter); + g_free (priv->local_comment); + g_free (priv->local_description); + g_free (priv->local_homepage); + g_free (priv->local_icon); G_OBJECT_CLASS (flatpak_remote_parent_class)->finalize (object); } diff --git a/common/flatpak-run.c b/common/flatpak-run.c index e4391019..8d613165 100644 --- a/common/flatpak-run.c +++ b/common/flatpak-run.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2014-2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -163,7 +163,6 @@ static void write_xauth (int family, const char *remote_host, const char *number, - const char *replace_number, FILE *output) { Xauth *xa, local_xa; @@ -191,11 +190,6 @@ write_xauth (int family, unames.nodename, number)) { local_xa = *xa; - if (local_xa.number != NULL && replace_number != NULL) - { - local_xa.number = (char *) replace_number; - local_xa.number_length = strlen (replace_number); - } if (local_xa.family == FamilyLocal && !auth_streq (unames.nodename, local_xa.address, local_xa.address_length)) @@ -319,19 +313,18 @@ flatpak_run_add_x11_args (FlatpakBwrap *bwrap, return; } - g_debug ("Allowing x11 access"); + g_info ("Allowing x11 access"); display = g_getenv ("DISPLAY"); if (display != NULL) { g_autofree char *remote_host = NULL; - g_autofree char *original_display_nr = NULL; - const char *replace_display_nr = NULL; + g_autofree char *display_nr = NULL; int family = -1; if (!flatpak_run_parse_x11_display (display, &family, &x11_socket, - &remote_host, &original_display_nr, + &remote_host, &display_nr, &local_error)) { g_warning ("%s", local_error->message); @@ -339,16 +332,16 @@ flatpak_run_add_x11_args (FlatpakBwrap *bwrap, return; } - g_assert (original_display_nr != NULL); + g_assert (display_nr != NULL); if (x11_socket != NULL && g_file_test (x11_socket, G_FILE_TEST_EXISTS)) { + g_assert (g_str_has_prefix (x11_socket, "/tmp/.X11-unix/X")); flatpak_bwrap_add_args (bwrap, - "--ro-bind", x11_socket, "/tmp/.X11-unix/X99", + "--ro-bind", x11_socket, x11_socket, NULL); - flatpak_bwrap_set_env (bwrap, "DISPLAY", ":99.0", TRUE); - replace_display_nr = "99"; + flatpak_bwrap_set_env (bwrap, "DISPLAY", display, TRUE); } else if ((shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0) { @@ -376,7 +369,7 @@ flatpak_run_add_x11_args (FlatpakBwrap *bwrap, } else { - flatpak_debug2 ("Assuming --share=network gives access to remote X11"); + g_debug ("Assuming --share=network gives access to remote X11"); } #ifdef ENABLE_XAUTH @@ -393,8 +386,7 @@ flatpak_run_add_x11_args (FlatpakBwrap *bwrap, { static const char dest[] = "/run/flatpak/Xauthority"; - write_xauth (family, remote_host, original_display_nr, - replace_display_nr, output); + write_xauth (family, remote_host, display_nr, output); flatpak_bwrap_add_args_data_fd (bwrap, "--ro-bind-data", tmp_fd, dest); flatpak_bwrap_set_env (bwrap, "XAUTHORITY", dest, TRUE); @@ -528,7 +520,7 @@ flatpak_run_get_cups_server_name_config (const char *path) input_stream = g_file_read (file, NULL, &my_error); if (my_error) { - g_debug ("CUPS configuration file '%s': %s", path, my_error->message); + g_info ("CUPS configuration file '%s': %s", path, my_error->message); return NULL; } @@ -600,7 +592,7 @@ flatpak_run_add_cups_args (FlatpakBwrap *bwrap) if (!g_file_test (cups_server_name, G_FILE_TEST_EXISTS)) { - g_debug ("Could not find CUPS server"); + g_info ("Could not find CUPS server"); return; } @@ -609,6 +601,46 @@ flatpak_run_add_cups_args (FlatpakBwrap *bwrap) NULL); } +static void +flatpak_run_add_gpg_agent_args (FlatpakBwrap *bwrap) +{ + const char * agent_socket; + g_autofree char * sandbox_agent_socket = NULL; + g_autoptr(GError) gpgconf_error = NULL; + g_autoptr(GSubprocess) process = NULL; + g_autoptr(GInputStream) base_stream = NULL; + g_autoptr(GDataInputStream) data_stream = NULL; + + process = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE, + &gpgconf_error, + "gpgconf", "--list-dir", "agent-socket", NULL); + + if (gpgconf_error) + { + g_info ("GPG-Agent directories: %s", gpgconf_error->message); + return; + } + + base_stream = g_subprocess_get_stdout_pipe (process); + data_stream = g_data_input_stream_new (base_stream); + + agent_socket = g_data_input_stream_read_line (data_stream, + NULL, NULL, + &gpgconf_error); + + if (!agent_socket || gpgconf_error) + { + g_info ("GPG-Agent directories: %s", gpgconf_error->message); + return; + } + + sandbox_agent_socket = g_strdup_printf ("/run/user/%d/gnupg/S.gpg-agent", getuid ()); + + flatpak_bwrap_add_args (bwrap, + "--ro-bind-try", agent_socket, sandbox_agent_socket, + NULL); +} + /* Try to find a default server from a pulseaudio confguration file */ static char * flatpak_run_get_pulseaudio_server_user_config (const char *path) @@ -622,7 +654,7 @@ flatpak_run_get_pulseaudio_server_user_config (const char *path) input_stream = g_file_read (file, NULL, &my_error); if (my_error) { - g_debug ("Pulseaudio user configuration file '%s': %s", path, my_error->message); + g_info ("Pulseaudio user configuration file '%s': %s", path, my_error->message); return NULL; } @@ -661,7 +693,7 @@ flatpak_run_get_pulseaudio_server_user_config (const char *path) if (strcmp ("default-server", tokens[0]) == 0) { g_strstrip (tokens[1]); - g_debug ("Found pulseaudio socket from configuration file '%s': %s", path, tokens[1]); + g_info ("Found pulseaudio socket from configuration file '%s': %s", path, tokens[1]); return g_strdup (tokens[1]); } } @@ -888,7 +920,7 @@ flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap, g_warning ("PulseAudio access will require --share=network permission."); } - g_debug ("Using remote PulseAudio server \"%s\"", pulseaudio_server); + g_info ("Using remote PulseAudio server \"%s\"", pulseaudio_server); flatpak_bwrap_set_env (bwrap, "PULSE_SERVER", pulseaudio_server, TRUE); } else if (pulseaudio_socket && g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS)) @@ -912,7 +944,7 @@ flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap, flatpak_bwrap_add_runtime_dir_member (bwrap, "pulse"); } else - g_debug ("Could not find pulseaudio socket"); + g_info ("Could not find pulseaudio socket"); /* Also allow ALSA access. This was added in 1.8, and is not ideally named. However, * since the practical permission of ALSA and PulseAudio are essentially the same, and @@ -924,6 +956,19 @@ flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap, } static void +flatpak_run_add_gssproxy_args (FlatpakBwrap *bwrap) +{ + /* We only expose the gssproxy user service. The gssproxy system service is + * not intended to be exposed to sandboxed environments. + */ + g_autofree char *gssproxy_host_dir = g_build_filename (g_get_user_runtime_dir (), "gssproxy", NULL); + const char *gssproxy_sandboxed_dir = "/run/flatpak/gssproxy/"; + + if (g_file_test (gssproxy_host_dir, G_FILE_TEST_EXISTS)) + flatpak_bwrap_add_args (bwrap, "--ro-bind", gssproxy_host_dir, gssproxy_sandboxed_dir, NULL); +} + +static void flatpak_run_add_resolved_args (FlatpakBwrap *bwrap) { const char *resolved_socket = "/run/systemd/resolve/io.systemd.Resolve"; @@ -985,7 +1030,7 @@ flatpak_run_add_system_dbus_args (FlatpakBwrap *app_bwrap, unrestricted = (context->sockets & FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS) != 0; if (unrestricted) - g_debug ("Allowing system-dbus access"); + g_info ("Allowing system-dbus access"); no_proxy = (flags & FLATPAK_RUN_FLAG_NO_SYSTEM_BUS_PROXY) != 0; @@ -1066,7 +1111,7 @@ flatpak_run_add_session_dbus_args (FlatpakBwrap *app_bwrap, } if (unrestricted) - g_debug ("Allowing session-dbus access"); + g_info ("Allowing session-dbus access"); no_proxy = (flags & FLATPAK_RUN_FLAG_NO_SESSION_BUS_PROXY) != 0; @@ -1319,7 +1364,7 @@ start_dbus_proxy (FlatpakBwrap *app_bwrap, flatpak_bwrap_finish (proxy_bwrap); commandline = flatpak_quote_argv ((const char **) proxy_bwrap->argv->pdata, -1); - g_debug ("Running '%s'", commandline); + g_info ("Running '%s'", commandline); /* We use LEAVE_DESCRIPTORS_OPEN to work around dead-lock, see flatpak_close_fds_workaround */ if (!g_spawn_async (NULL, @@ -1552,13 +1597,13 @@ flatpak_run_add_environment_args (FlatpakBwrap *bwrap, if ((context->shares & FLATPAK_CONTEXT_SHARED_IPC) == 0) { - g_debug ("Disallowing ipc access"); + g_info ("Disallowing ipc access"); flatpak_bwrap_add_args (bwrap, "--unshare-ipc", NULL); } if ((context->shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0) { - g_debug ("Disallowing network access"); + g_info ("Disallowing network access"); flatpak_bwrap_add_args (bwrap, "--unshare-net", NULL); } @@ -1655,7 +1700,7 @@ flatpak_run_add_environment_args (FlatpakBwrap *bwrap, NULL); if (context->devices & FLATPAK_CONTEXT_DEVICE_DRI) { - g_debug ("Allowing dri access"); + g_info ("Allowing dri access"); int i; char *dri_devices[] = { "/dev/dri", @@ -1690,7 +1735,7 @@ flatpak_run_add_environment_args (FlatpakBwrap *bwrap, if (context->devices & FLATPAK_CONTEXT_DEVICE_KVM) { - g_debug ("Allowing kvm access"); + g_info ("Allowing kvm access"); if (g_file_test ("/dev/kvm", G_FILE_TEST_EXISTS)) flatpak_bwrap_add_args (bwrap, "--dev-bind", "/dev/kvm", "/dev/kvm", NULL); } @@ -1700,7 +1745,7 @@ flatpak_run_add_environment_args (FlatpakBwrap *bwrap, /* This is a symlink to /run/shm on debian, so bind to real target */ g_autofree char *real_dev_shm = realpath ("/dev/shm", NULL); - g_debug ("Allowing /dev/shm access (as %s)", real_dev_shm); + g_info ("Allowing /dev/shm access (as %s)", real_dev_shm); if (real_dev_shm != NULL) flatpak_bwrap_add_args (bwrap, "--bind", real_dev_shm, "/dev/shm", NULL); } @@ -1759,7 +1804,7 @@ flatpak_run_add_environment_args (FlatpakBwrap *bwrap, if (context->sockets & FLATPAK_CONTEXT_SOCKET_WAYLAND) { - g_debug ("Allowing wayland access"); + g_info ("Allowing wayland access"); has_wayland = flatpak_run_add_wayland_args (bwrap); } @@ -1777,7 +1822,7 @@ flatpak_run_add_environment_args (FlatpakBwrap *bwrap, if (context->sockets & FLATPAK_CONTEXT_SOCKET_PULSEAUDIO) { - g_debug ("Allowing pulseaudio access"); + g_info ("Allowing pulseaudio access"); flatpak_run_add_pulseaudio_args (bwrap, context->shares); } @@ -1791,6 +1836,11 @@ flatpak_run_add_environment_args (FlatpakBwrap *bwrap, flatpak_run_add_cups_args (bwrap); } + if (context->sockets & FLATPAK_CONTEXT_SOCKET_GPG_AGENT) + { + flatpak_run_add_gpg_agent_args (bwrap); + } + flatpak_run_add_session_dbus_args (bwrap, proxy_arg_bwrap, context, flags, app_id); flatpak_run_add_system_dbus_args (bwrap, proxy_arg_bwrap, context, flags); flatpak_run_add_a11y_dbus_args (bwrap, proxy_arg_bwrap, context, flags); @@ -1801,7 +1851,7 @@ flatpak_run_add_environment_args (FlatpakBwrap *bwrap, { /* We still run along even if we don't get a cgroup, as nothing really depends on it. Its just nice to have */ - g_debug ("Failed to run in transient scope: %s", my_error->message); + g_info ("Failed to run in transient scope: %s", my_error->message); g_clear_error (&my_error); } @@ -1830,14 +1880,19 @@ static const ExportData default_exports[] = { {"XDG_CONFIG_DIRS", "/app/etc/xdg:/etc/xdg"}, {"XDG_DATA_DIRS", "/app/share:/usr/share"}, {"SHELL", "/bin/sh"}, - {"TMPDIR", NULL}, /* Unset TMPDIR as it may not exist in the sandbox */ + /* Unset temporary file paths as they may not exist in the sandbox */ + {"TEMP", NULL}, + {"TEMPDIR", NULL}, + {"TMP", NULL}, + {"TMPDIR", NULL}, /* We always use /run/user/UID, even if the user's XDG_RUNTIME_DIR * outside the sandbox is somewhere else. Don't allow a different * setting from outside the sandbox to overwrite this. */ {"XDG_RUNTIME_DIR", NULL}, /* Some env vars are common enough and will affect the sandbox badly - if set on the host. We clear these always. */ + if set on the host. We clear these always. If updating this list, + also update the list in flatpak-run.xml. */ {"PYTHONPATH", NULL}, {"PERLLIB", NULL}, {"PERL5LIB", NULL}, @@ -1854,6 +1909,9 @@ static const ExportData default_exports[] = { {"GST_PTP_HELPER", NULL}, {"GST_PTP_HELPER_1_0", NULL}, {"GST_INSTALL_PLUGINS_HELPER", NULL}, + {"KRB5CCNAME", NULL}, + {"XKB_CONFIG_ROOT", NULL}, + {"GIO_EXTRA_MODULES", NULL}, }; static const ExportData no_ld_so_cache_exports[] = { @@ -2456,17 +2514,17 @@ get_dconf_data (const char *app_id, if (migrate_path) { - g_debug ("Add values in dir '%s', prefix is '%s'", migrate_path, prefix); + g_info ("Add values in dir '%s', prefix is '%s'", migrate_path, prefix); if (flatpak_dconf_path_is_similar (migrate_path, prefix)) add_dconf_dir_to_keyfile (values_data, client, migrate_path, DCONF_READ_USER_VALUE); else g_warning ("Ignoring D-Conf migrate-path setting %s", migrate_path); } - g_debug ("Add defaults in dir %s", prefix); + g_info ("Add defaults in dir %s", prefix); add_dconf_dir_to_keyfile (defaults_data, client, prefix, DCONF_READ_DEFAULT_VALUE); - g_debug ("Add locks in dir %s", prefix); + g_info ("Add locks in dir %s", prefix); add_dconf_locks_to_list (locks_data, client, prefix); /* We allow extra paths for defaults and locks, but not for user values */ @@ -2477,15 +2535,15 @@ get_dconf_data (const char *app_id, { if (dconf_is_dir (paths[i], NULL)) { - g_debug ("Add defaults in dir %s", paths[i]); + g_info ("Add defaults in dir %s", paths[i]); add_dconf_dir_to_keyfile (defaults_data, client, paths[i], DCONF_READ_DEFAULT_VALUE); - g_debug ("Add locks in dir %s", paths[i]); + g_info ("Add locks in dir %s", paths[i]); add_dconf_locks_to_list (locks_data, client, paths[i]); } else if (dconf_is_key (paths[i], NULL)) { - g_debug ("Add individual key %s", paths[i]); + g_info ("Add individual key %s", paths[i]); add_dconf_key_to_keyfile (defaults_data, client, paths[i], DCONF_READ_DEFAULT_VALUE); add_dconf_key_to_keyfile (values_data, client, paths[i], DCONF_READ_USER_VALUE); } @@ -2569,7 +2627,7 @@ flatpak_run_add_dconf_args (FlatpakBwrap *bwrap, "config/glib-2.0/settings/keyfile", NULL); - g_debug ("writing D-Conf values to %s", filename); + g_info ("writing D-Conf values to %s", filename); if (values_size != 0 && !g_file_test (filename, G_FILE_TEST_EXISTS)) { @@ -3000,7 +3058,7 @@ add_document_portal_args (FlatpakBwrap *bwrap, if (g_dbus_message_to_gerror (reply, &local_error)) { if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) - g_debug ("Document portal not available, not mounting /run/flatpak/doc"); + g_info ("Document portal not available, not mounting /run/flatpak/doc"); else g_message ("Can't get document portal: %s", local_error->message); } @@ -3127,9 +3185,6 @@ setup_seccomp (FlatpakBwrap *bwrap, {SCMP_SYS (uselib), EPERM}, /* Don't allow disabling accounting */ {SCMP_SYS (acct), EPERM}, - /* 16-bit code is unnecessary in the sandbox, and modify_ldt is a - historic source of interesting information leaks. */ - {SCMP_SYS (modify_ldt), EPERM}, /* Don't allow reading current quota use */ {SCMP_SYS (quotactl), EPERM}, @@ -3295,8 +3350,25 @@ setup_seccomp (FlatpakBwrap *bwrap, * libseccomp cannot map the syscall number to a name and back to a * number for the non-native architecture. */ if (r == -EFAULT) - flatpak_debug2 ("Unable to block syscall %d: syscall not known to libseccomp?", - scall); + g_debug ("Unable to block syscall %d: syscall not known to libseccomp?", + scall); + else if (r < 0) + return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Failed to block syscall %d: %s"), scall, flatpak_seccomp_strerror (r)); + } + + if (!multiarch) + { + /* modify_ldt is a historic source of interesting information leaks, + * so it's disabled as a hardening measure. + * However, it is required to run old 16-bit applications + * as well as some Wine patches, so it's allowed in multiarch. */ + int scall = SCMP_SYS (modify_ldt); + r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0); + + /* See above for the meaning of EFAULT. */ + if (r == -EFAULT) + g_debug ("Unable to block syscall %d: syscall not known to libseccomp?", + scall); else if (r < 0) return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Failed to block syscall %d: %s"), scall, flatpak_seccomp_strerror (r)); } @@ -3317,8 +3389,8 @@ setup_seccomp (FlatpakBwrap *bwrap, /* See above for the meaning of EFAULT. */ if (r == -EFAULT) - flatpak_debug2 ("Unable to block syscall %d: syscall not known to libseccomp?", - scall); + g_debug ("Unable to block syscall %d: syscall not known to libseccomp?", + scall); else if (r < 0) return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Failed to block syscall %d: %s"), scall, flatpak_seccomp_strerror (r)); } @@ -3399,8 +3471,8 @@ flatpak_run_setup_usr_links (FlatpakBwrap *bwrap, } else { - g_debug ("%s does not exist", - flatpak_file_get_path_cached (runtime_subdir)); + g_info ("%s does not exist", + flatpak_file_get_path_cached (runtime_subdir)); } } } @@ -3572,7 +3644,7 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, if ((flags & FLATPAK_RUN_FLAG_SET_PERSONALITY) && flatpak_is_linux32_arch (arch)) { - g_debug ("Setting personality linux32"); + g_info ("Setting personality linux32"); pers = PER_LINUX32; } @@ -3726,7 +3798,7 @@ add_rest_args (FlatpakBwrap *bwrap, g_assert (doc_path != NULL); } - g_debug ("Forwarding file '%s' as '%s' to %s", args[i], doc_path, app_id); + g_info ("Forwarding file '%s' as '%s' to %s", args[i], doc_path, app_id); flatpak_bwrap_add_arg (bwrap, doc_path); } else @@ -3822,7 +3894,7 @@ regenerate_ld_cache (GPtrArray *base_argv_array, if (ld_so_fd >= 0) return glnx_steal_fd (&ld_so_fd); - g_debug ("Regenerating ld.so.cache %s", flatpak_file_get_path_cached (ld_so_cache)); + g_info ("Regenerating ld.so.cache %s", flatpak_file_get_path_cached (ld_so_cache)); if (!flatpak_mkdir_p (ld_so_dir, cancellable, error)) return FALSE; @@ -3870,7 +3942,7 @@ regenerate_ld_cache (GPtrArray *base_argv_array, flatpak_bwrap_finish (bwrap); commandline = flatpak_quote_argv ((const char **) bwrap->argv->pdata, -1); - g_debug ("Running: '%s'", commandline); + g_info ("Running: '%s'", commandline); combined_fd_array = g_array_new (FALSE, TRUE, sizeof (int)); g_array_append_vals (combined_fd_array, base_fd_array->data, base_fd_array->len); @@ -3953,15 +4025,15 @@ check_parental_controls (FlatpakDecomposed *app_ref, 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", flatpak_decomposed_get_ref (app_ref)); + g_info ("Skipping parental controls check for %s since parental " + "controls are disabled globally", flatpak_decomposed_get_ref (app_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", flatpak_decomposed_get_ref (app_ref)); + g_info ("Skipping parental controls check for %s since a required " + "service was not found", flatpak_decomposed_get_ref (app_ref)); return TRUE; } else if (local_error != NULL) @@ -4560,7 +4632,10 @@ flatpak_run_app (FlatpakDecomposed *app_ref, } if ((app_context->shares & FLATPAK_CONTEXT_SHARED_NETWORK) != 0) - flatpak_run_add_resolved_args (bwrap); + { + flatpak_run_add_gssproxy_args (bwrap); + flatpak_run_add_resolved_args (bwrap); + } flatpak_run_add_journal_args (bwrap); add_font_path_args (bwrap); @@ -4644,7 +4719,7 @@ flatpak_run_app (FlatpakDecomposed *app_ref, flatpak_bwrap_finish (bwrap); commandline = flatpak_quote_argv ((const char **) bwrap->argv->pdata, -1); - g_debug ("Running '%s'", commandline); + g_info ("Running '%s'", commandline); if ((flags & (FLATPAK_RUN_FLAG_BACKGROUND)) != 0 || g_getenv ("FLATPAK_TEST_COVERAGE") != NULL) diff --git a/common/flatpak-transaction.c b/common/flatpak-transaction.c index 2239ea2d..97d79bbc 100644 --- a/common/flatpak-transaction.c +++ b/common/flatpak-transaction.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2016 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -22,7 +22,6 @@ #include <stdio.h> #include <glib/gi18n-lib.h> -#include <libsoup/soup.h> #include "flatpak-auth-private.h" #include "flatpak-error.h" @@ -30,6 +29,7 @@ #include "flatpak-progress-private.h" #include "flatpak-transaction-private.h" #include "flatpak-utils-private.h" +#include "flatpak-uri-private.h" #include "flatpak-variant-impl-private.h" /** @@ -704,12 +704,13 @@ flatpak_transaction_operation_get_decomposed (FlatpakTransactionOperation *self) * flatpak_transaction_operation_get_related_to_ops: * @self: a #FlatpakTransactionOperation * - * Gets the operations which caused this operation to be added to the - * transaction. In the case of a runtime, it's the apps whose runtime it is (and - * this could be multiple apps, if they all require the same runtime). In - * the case of a related ref such as an extension, it's the main app or - * runtime. In the case of a main app or something added to the transaction by - * flatpak_transaction_add_ref(), %NULL or an empty array will be returned. + * Gets the operation(s) which caused this operation to be added to the + * transaction. In the case of a runtime, it's the app(s) whose runtime it is, + * and/or a runtime extension in the special case of an extra-data extension + * that doesn't define the "NoRuntime" key. In the case of a related ref such + * as an extension, it's the main app or runtime. In the case of a main app or + * something added to the transaction by e.g. flatpak_transaction_add_install() + * and which is not otherwise needed, %NULL or an empty array will be returned. * * Note that an op will be returned even if it’s marked as to be skipped when * the transaction is run. Check that using @@ -1317,15 +1318,17 @@ flatpak_transaction_class_init (FlatpakTransactionClass *klass) * FlatpakTransaction::ready-pre-auth: * @object: A #FlatpakTransaction * - * The ::ready-pre-auth signal is emitted when all the refs involved in the transaction - * have been resolved to commits, but we might not necessarily have asked for authenticaion - * for all their required operations. This is very similar to the ::ready signal, and you can - * chose which one (or both) to use depending on how you want to handle authentication in your user + * The ::ready-pre-auth signal is emitted when all the refs involved in the + * transaction have been resolved to commits, but we might not necessarily + * have asked for authentication for all their required operations. This is + * very similar to the ::ready signal, and you can choose which one (or both) + * to use depending on how you want to handle authentication in your user * interface. * - * At this point flatpak_transaction_get_operations() will return all the operations - * that will be executed as part of the transaction. You can call flatpak_transaction_operation_get_requires_authentication() - * to see which will require authentication. + * At this point flatpak_transaction_get_operations() will return all the + * operations that will be executed as part of the transaction. You can call + * flatpak_transaction_operation_get_requires_authentication() to see which + * will require authentication. * * Returns: %TRUE to carry on with the transaction, %FALSE to abort * @@ -2118,11 +2121,11 @@ flatpak_transaction_add_op (FlatpakTransaction *self, g_autofree char *subpaths_str = NULL; subpaths_str = subpaths_to_string (subpaths); - g_debug ("Transaction: %s %s:%s%s%s%s", - kind_to_str (kind), remote, flatpak_decomposed_get_ref (ref), - commit != NULL ? "@" : "", - commit != NULL ? commit : "", - subpaths_str); + g_info ("Transaction: %s %s:%s%s%s%s", + kind_to_str (kind), remote, flatpak_decomposed_get_ref (ref), + commit != NULL ? "@" : "", + commit != NULL ? commit : "", + subpaths_str); op = flatpak_transaction_get_last_op_for_ref (self, ref); /* If previous_ids is given, then this is a rebase operation. */ @@ -2189,7 +2192,7 @@ op_get_related (FlatpakTransaction *self, if (op->resolved_metakey == NULL) { - g_debug ("no resolved metadata for related to %s", flatpak_decomposed_get_ref (op->ref)); + g_info ("no resolved metadata for related to %s", flatpak_decomposed_get_ref (op->ref)); return TRUE; } @@ -2342,7 +2345,7 @@ search_for_dependency (FlatpakTransaction *self, state = flatpak_transaction_ensure_remote_state (self, FLATPAK_TRANSACTION_OPERATION_INSTALL, remote, arch, &local_error); if (state == NULL) { - g_debug ("Can't get state for remote %s, ignoring: %s", remote, local_error->message); + g_info ("Can't get state for remote %s, ignoring: %s", remote, local_error->message); continue; } @@ -2465,7 +2468,7 @@ op_get_runtime_ref (FlatpakTransactionOperation *op) decomposed = flatpak_decomposed_new_from_pref (FLATPAK_KINDS_RUNTIME, runtime_pref, NULL); if (decomposed == NULL) - g_debug ("Invalid runtime ref %s in metadata", runtime_pref); + g_info ("Invalid runtime ref %s in metadata", runtime_pref); return decomposed; } @@ -2485,7 +2488,7 @@ op_get_sdk_ref (FlatpakTransactionOperation *op) decomposed = flatpak_decomposed_new_from_pref (FLATPAK_KINDS_RUNTIME, sdk_pref, NULL); if (decomposed == NULL) - g_debug ("Invalid runtime ref %s in metadata", sdk_pref); + g_info ("Invalid runtime ref %s in metadata", sdk_pref); return decomposed; } @@ -2502,8 +2505,8 @@ add_new_dep_op (FlatpakTransaction *self, if (!ref_is_installed (self, dep_ref)) { - g_debug ("Installing dependency %s of %s", flatpak_decomposed_get_pref (dep_ref), - flatpak_decomposed_get_pref (op->ref)); + g_info ("Installing dependency %s of %s", flatpak_decomposed_get_pref (dep_ref), + flatpak_decomposed_get_pref (op->ref)); dep_remote = find_runtime_remote (self, op->ref, op->remote, dep_ref, op->kind, NULL, error); if (dep_remote == NULL) return FALSE; @@ -2518,8 +2521,8 @@ add_new_dep_op (FlatpakTransaction *self, /* Update if in same dir */ if (dir_ref_is_installed (priv->dir, dep_ref, &dep_remote, NULL)) { - g_debug ("Updating dependency %s of %s", flatpak_decomposed_get_pref (dep_ref), - flatpak_decomposed_get_pref (op->ref)); + g_info ("Updating dependency %s of %s", flatpak_decomposed_get_pref (dep_ref), + flatpak_decomposed_get_pref (op->ref)); *dep_op = flatpak_transaction_add_op (self, dep_remote, dep_ref, NULL, NULL, NULL, NULL, FLATPAK_TRANSACTION_OPERATION_UPDATE, FALSE, error); if (*dep_op == NULL) @@ -2665,7 +2668,7 @@ flatpak_transaction_add_ref (FlatpakTransaction *self, if (flatpak_dir_get_remote_disabled (priv->dir, origin)) { - g_debug (_("Remote %s disabled, ignoring %s update"), origin, pref); + g_info (_("Remote %s disabled, ignoring %s update"), origin, pref); return TRUE; } remote = origin; @@ -3003,13 +3006,13 @@ flatpak_transaction_update_metadata (FlatpakTransaction *self, g_autoptr(GError) my_error = NULL; g_autoptr(FlatpakRemoteState) state = flatpak_transaction_ensure_remote_state (self, FLATPAK_TRANSACTION_OPERATION_UPDATE, remote, NULL, NULL); - g_debug ("Looking for remote metadata updates for %s", remote); + g_info ("Looking for remote metadata updates for %s", remote); if (!flatpak_dir_update_remote_configuration (priv->dir, remote, state, &updated, cancellable, &my_error)) - g_debug (_("Error updating remote metadata for '%s': %s"), remote, my_error->message); + g_info (_("Error updating remote metadata for '%s': %s"), remote, my_error->message); if (updated) { - g_debug ("Got updated metadata for %s", remote); + g_info ("Got updated metadata for %s", remote); some_updated = TRUE; } } @@ -3067,13 +3070,13 @@ flatpak_transaction_add_auto_install (FlatpakTransaction *self, if (state != NULL && flatpak_remote_state_lookup_ref (state, flatpak_decomposed_get_ref (auto_install_ref), NULL, NULL, NULL, NULL, NULL)) { - g_debug ("Auto adding install of %s from remote %s", flatpak_decomposed_get_ref (auto_install_ref), remote); + g_info ("Auto adding install of %s from remote %s", flatpak_decomposed_get_ref (auto_install_ref), remote); if (!flatpak_transaction_add_ref (self, remote, auto_install_ref, NULL, NULL, NULL, FLATPAK_TRANSACTION_OPERATION_INSTALL_OR_UPDATE, NULL, NULL, FALSE, &local_error)) - g_debug ("Failed to add auto-install ref %s: %s", flatpak_decomposed_get_ref (auto_install_ref), + g_info ("Failed to add auto-install ref %s: %s", flatpak_decomposed_get_ref (auto_install_ref), local_error->message); } } @@ -3141,7 +3144,7 @@ load_deployed_metadata (FlatpakTransaction *self, FlatpakDecomposed *ref, char * if (!g_file_load_contents (metadata_file, NULL, &metadata_contents, &metadata_contents_length, NULL, NULL)) { - g_debug ("No metadata in local deploy of %s", flatpak_decomposed_get_ref (ref)); + g_info ("No metadata in local deploy of %s", flatpak_decomposed_get_ref (ref)); return NULL; } @@ -3173,7 +3176,7 @@ mark_op_resolved (FlatpakTransactionOperation *op, GBytes *old_metadata, GError **error) { - g_debug ("marking op %s:%s resolved to %s", kind_to_str (op->kind), flatpak_decomposed_get_ref (op->ref), commit ? commit : "-"); + g_info ("marking op %s:%s resolved to %s", kind_to_str (op->kind), flatpak_decomposed_get_ref (op->ref), commit ? commit : "-"); g_assert (op != NULL); @@ -3267,6 +3270,19 @@ resolve_op_from_commit (FlatpakTransaction *self, g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE, "s", &op->eol); g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE, "s", &op->eol_rebase); + if (op->eol_rebase) + { + g_autoptr(FlatpakDecomposed) eolr_decomposed = NULL; + eolr_decomposed = flatpak_decomposed_new_from_ref (op->eol_rebase, error); + if (!eolr_decomposed) + return FALSE; + if (flatpak_decomposed_get_kind (op->ref) != flatpak_decomposed_get_kind (eolr_decomposed)) + return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, + "end-of-life-rebase on commit %s has the wrong type (%s -> %s)", + checksum, flatpak_decomposed_get_ref (op->ref), + flatpak_decomposed_get_ref (eolr_decomposed)); + } + return resolve_op_end (self, op, checksum, sideload_path, metadata_bytes, error); } @@ -3317,6 +3333,19 @@ try_resolve_op_from_metadata (FlatpakTransaction *self, op->eol = g_strdup (var_metadata_lookup_string (sparse_cache, FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE, NULL)); op->eol_rebase = g_strdup (var_metadata_lookup_string (sparse_cache, FLATPAK_SPARSE_CACHE_KEY_ENDOFLINE_REBASE, NULL)); op->token_type = GINT32_FROM_LE (var_metadata_lookup_int32 (sparse_cache, FLATPAK_SPARSE_CACHE_KEY_TOKEN_TYPE, op->token_type)); + + if (op->eol_rebase) + { + g_autoptr(FlatpakDecomposed) eolr_decomposed = NULL; + eolr_decomposed = flatpak_decomposed_new_from_ref (op->eol_rebase, error); + if (!eolr_decomposed) + return FALSE; + if (flatpak_decomposed_get_kind (op->ref) != flatpak_decomposed_get_kind (eolr_decomposed)) + return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, + "end-of-life-rebase on commit %s has the wrong type (%s -> %s)", + checksum, flatpak_decomposed_get_ref (op->ref), + flatpak_decomposed_get_ref (eolr_decomposed)); + } } return resolve_op_end (self, op, checksum, sideload_path, metadata_bytes, error); @@ -3449,7 +3478,7 @@ resolve_ops (FlatpakTransaction *self, if (latest_sideload_path != NULL && local_commit_data && latest_timestamp != 0 && ostree_commit_get_timestamp (local_commit_data) > latest_timestamp) { - g_debug ("Installed commit %s newer than sideloaded %s, ignoring", local_checksum, latest_checksum); + g_info ("Installed commit %s newer than sideloaded %s, ignoring", local_checksum, latest_checksum); checksum = g_steal_pointer (&local_checksum); } else @@ -3507,7 +3536,7 @@ resolve_ops (FlatpakTransaction *self, if (g_error_matches (local_error, FLATPAK_HTTP_ERROR, FLATPAK_HTTP_ERROR_UNAUTHORIZED) && !op->requested_token) { - g_debug ("Unauthorized access during resolve by commit of %s, retrying with token", flatpak_decomposed_get_ref (op->ref)); + g_info ("Unauthorized access during resolve by commit of %s, retrying with token", flatpak_decomposed_get_ref (op->ref)); priv->needs_resolve = TRUE; priv->needs_tokens = TRUE; @@ -3593,7 +3622,7 @@ request_tokens_webflow (FlatpakAuthenticatorRequest *object, g_assert (priv->active_request_id == 0); priv->active_request_id = ++priv->next_request_id; - g_debug ("Webflow start %s", arg_uri); + g_info ("Webflow start %s", arg_uri); g_signal_emit (transaction, signals[WEBFLOW_START], 0, data->remote, arg_uri, options, priv->active_request_id, &retval); if (!retval) { @@ -3603,7 +3632,7 @@ request_tokens_webflow (FlatpakAuthenticatorRequest *object, /* We didn't handle the uri, cancel the auth op. */ if (!flatpak_authenticator_request_call_close_sync (data->request, NULL, &local_error)) - g_debug ("Failed to close auth request: %s", local_error->message); + g_info ("Failed to close auth request: %s", local_error->message); } } @@ -3623,7 +3652,7 @@ request_tokens_webflow_done (FlatpakAuthenticatorRequest *object, id = priv->active_request_id; priv->active_request_id = 0; - g_debug ("Webflow done"); + g_info ("Webflow done"); g_signal_emit (transaction, signals[WEBFLOW_DONE], 0, options, id); } @@ -3643,7 +3672,7 @@ request_tokens_basic_auth (FlatpakAuthenticatorRequest *object, g_assert (priv->active_request_id == 0); priv->active_request_id = ++priv->next_request_id; - g_debug ("BasicAuth start %s", arg_realm); + g_info ("BasicAuth start %s", arg_realm); g_signal_emit (transaction, signals[BASIC_AUTH_START], 0, data->remote, arg_realm, options, priv->active_request_id, &retval); if (!retval) { @@ -3653,7 +3682,7 @@ request_tokens_basic_auth (FlatpakAuthenticatorRequest *object, /* We didn't handle the request, cancel the auth op. */ if (!flatpak_authenticator_request_call_close_sync (data->request, NULL, &local_error)) - g_debug ("Failed to close auth request: %s", local_error->message); + g_info ("Failed to close auth request: %s", local_error->message); } } @@ -3690,7 +3719,7 @@ flatpak_transaction_abort_webflow (FlatpakTransaction *self, if (!data->done) { if (!flatpak_authenticator_request_call_close_sync (data->request, NULL, &local_error)) - g_debug ("Failed to close auth request: %s", local_error->message); + g_info ("Failed to close auth request: %s", local_error->message); } } } @@ -3734,7 +3763,7 @@ flatpak_transaction_complete_basic_auth (FlatpakTransaction *self, if (user == NULL) { if (!flatpak_authenticator_request_call_close_sync (data->request, NULL, &local_error)) - g_debug ("Failed to abort basic auth request: %s", local_error->message); + g_info ("Failed to abort basic auth request: %s", local_error->message); } else { @@ -3742,7 +3771,7 @@ flatpak_transaction_complete_basic_auth (FlatpakTransaction *self, user, password, options, NULL, &local_error)) - g_debug ("Failed to reply to basic auth request: %s", local_error->message); + g_info ("Failed to reply to basic auth request: %s", local_error->message); } } } @@ -3829,7 +3858,7 @@ request_tokens_for_remote (FlatpakTransaction *self, g_string_append (refs_as_str, ", "); } - g_debug ("Requesting tokens for remote %s: %s", remote, refs_as_str->str); + g_info ("Requesting tokens for remote %s: %s", remote, refs_as_str->str); refs = g_variant_ref_sink (g_variant_builder_end (&refs_builder)); extra_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); @@ -3875,7 +3904,7 @@ request_tokens_for_remote (FlatpakTransaction *self, { g_autofree char *results_str = results != NULL ? g_variant_print (results, FALSE) : g_strdup ("NULL"); - g_debug ("Response from request_tokens: %d - %s\n", data.response, results_str); + g_info ("Response from request_tokens: %d - %s\n", data.response, results_str); } if (data.response == FLATPAK_AUTH_RESPONSE_CANCELLED) @@ -4116,6 +4145,71 @@ flatpak_transaction_get_current_operation (FlatpakTransaction *self) } /** + * flatpak_transaction_get_operation_for_ref: + * @self: a #FlatpakTransaction + * @remote: (nullable): a remote name + * @ref: a ref + * @error: return location for an error + * + * Gets the operation for @ref, if any match. If @remote is non-%NULL, only an + * operation for that remote will be returned. If remote is %NULL and the + * transaction has more than one operation for @ref from different remotes, an + * error will be returned. + * + * Returns: (transfer full): the #FlatpakTransactionOperation for @ref, or + * %NULL with @error set + * Since: 1.13.3 + */ +FlatpakTransactionOperation * +flatpak_transaction_get_operation_for_ref (FlatpakTransaction *self, + const char *remote, + const char *ref, + GError **error) +{ + FlatpakTransactionPrivate *priv = flatpak_transaction_get_instance_private (self); + g_autoptr(FlatpakDecomposed) decomposed_ref = NULL; + g_autoptr(FlatpakTransactionOperation) matching_op = NULL; + GList *l; + + g_return_val_if_fail (ref != NULL, NULL); + + decomposed_ref = flatpak_decomposed_new_from_ref (ref, error); + if (decomposed_ref == NULL) + return NULL; + + for (l = priv->ops; l != NULL; l = l->next) + { + FlatpakTransactionOperation *op = l->data; + + if (remote != NULL && g_strcmp0 (remote, op->remote) != 0) + continue; + + if (flatpak_decomposed_equal (op->ref, decomposed_ref)) + { + if (matching_op == NULL) + matching_op = g_object_ref (op); + else + { + flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, + _("Ref %s from %s matches more than one transaction operation"), + ref, remote ? remote : _("any remote")); + return NULL; + } + } + } + + if (matching_op == NULL) + { + flatpak_fail_error (error, FLATPAK_ERROR_REF_NOT_FOUND, + _("No transaction operation found for ref %s from %s"), + ref, remote ? remote : _("any remote")); + return NULL; + } + + return g_steal_pointer (&matching_op); +} + +/** * flatpak_transaction_get_installation: * @self: a #FlatpakTransactionOperation * @@ -4222,7 +4316,7 @@ load_flatpakrepo_file (FlatpakTransaction *self, g_autoptr(GBytes) dep_data = NULL; g_autoptr(GKeyFile) dep_keyfile = g_key_file_new (); g_autoptr(GError) local_error = NULL; - g_autoptr(SoupSession) soup_session = NULL; + g_autoptr(FlatpakHttpSession) http_session = NULL; if (priv->disable_deps) return TRUE; @@ -4232,8 +4326,8 @@ load_flatpakrepo_file (FlatpakTransaction *self, !g_str_has_prefix (dep_url, "file:")) return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Flatpakrepo URL %s not file, HTTP or HTTPS"), dep_url); - soup_session = flatpak_create_soup_session (PACKAGE_STRING); - dep_data = flatpak_load_uri (soup_session, dep_url, 0, NULL, NULL, NULL, NULL, cancellable, error); + http_session = flatpak_create_http_session (PACKAGE_STRING); + dep_data = flatpak_load_uri (http_session, dep_url, 0, NULL, NULL, NULL, NULL, cancellable, error); if (dep_data == NULL) { g_prefix_error (error, _("Can't load dependent file %s: "), dep_url); @@ -4264,7 +4358,7 @@ handle_runtime_repo_deps (FlatpakTransaction *self, g_autofree char *runtime_url = NULL; g_autofree char *new_remote = NULL; g_autofree char *basename = NULL; - g_autoptr(SoupURI) uri = NULL; + g_autoptr(GUri) uri = NULL; g_auto(GStrv) remotes = NULL; g_autoptr(GKeyFile) config = NULL; g_autoptr(GBytes) gpg_key = NULL; @@ -4278,8 +4372,8 @@ handle_runtime_repo_deps (FlatpakTransaction *self, g_assert (dep_keyfile != NULL); - uri = soup_uri_new (dep_url); - basename = g_path_get_basename (soup_uri_get_path (uri)); + uri = g_uri_parse (dep_url, FLATPAK_HTTP_URI_FLAGS | G_URI_FLAGS_PARSE_RELAXED, NULL); + basename = g_path_get_basename (g_uri_get_path (uri)); /* Strip suffix */ t = strchr (basename, '.'); if (t != NULL) @@ -4661,7 +4755,7 @@ _run_op_kind (FlatpakTransaction *self, } } else - g_debug ("%s need no update", flatpak_decomposed_get_ref (op->ref)); + g_info ("%s need no update", flatpak_decomposed_get_ref (op->ref)); } else if (op->kind == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE) { diff --git a/common/flatpak-transaction.h b/common/flatpak-transaction.h index 6f3ec544..c5832dc8 100644 --- a/common/flatpak-transaction.h +++ b/common/flatpak-transaction.h @@ -280,6 +280,11 @@ gboolean flatpak_transaction_run (FlatpakTransaction *transaction, FLATPAK_EXTERN FlatpakTransactionOperation *flatpak_transaction_get_current_operation (FlatpakTransaction *self); FLATPAK_EXTERN +FlatpakTransactionOperation *flatpak_transaction_get_operation_for_ref (FlatpakTransaction *self, + const char *remote, + const char *ref, + GError **error); +FLATPAK_EXTERN FlatpakInstallation *flatpak_transaction_get_installation (FlatpakTransaction *self); FLATPAK_EXTERN GList *flatpak_transaction_get_operations (FlatpakTransaction *self); diff --git a/common/flatpak-uri-private.h b/common/flatpak-uri-private.h new file mode 100644 index 00000000..fa3afee5 --- /dev/null +++ b/common/flatpak-uri-private.h @@ -0,0 +1,125 @@ +/* + * Copyright © 2022 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Alexander Larsson <alexl@redhat.com> + */ + +#ifndef __FLATPAK_URI_PRIVATE_H__ +#define __FLATPAK_URI_PRIVATE_H__ + +#include <string.h> + +#include "flatpak-utils-private.h" + +/* This file is mainly a backport of GUri for older versions of glib, and some helpers around it */ + +void flatpak_uri_encode_query_arg (GString *query, + const char *key, + const char *value); +GHashTable *flatpak_parse_http_header_param_list (const char *header); +GDateTime * flatpak_parse_http_time (const char *date_string); +char * flatpak_format_http_date (GDateTime *date); + +/* Same as SOUP_HTTP_URI_FLAGS, means all possible flags for http uris */ +#if GLIB_CHECK_VERSION (2, 68, 0) || !GLIB_CHECK_VERSION (2, 66, 0) +#define FLATPAK_HTTP_URI_FLAGS (G_URI_FLAGS_HAS_PASSWORD | G_URI_FLAGS_ENCODED_PATH | G_URI_FLAGS_ENCODED_QUERY | G_URI_FLAGS_ENCODED_FRAGMENT | G_URI_FLAGS_SCHEME_NORMALIZE) +#else +/* GLib 2.66 didn't support scheme-based normalization */ +#define FLATPAK_HTTP_URI_FLAGS (G_URI_FLAGS_HAS_PASSWORD | G_URI_FLAGS_ENCODED_PATH | G_URI_FLAGS_ENCODED_QUERY | G_URI_FLAGS_ENCODED_FRAGMENT) +#endif + +#if !GLIB_CHECK_VERSION (2, 66, 0) + +typedef enum { + G_URI_FLAGS_NONE = 0, + G_URI_FLAGS_PARSE_RELAXED = 1 << 0, + G_URI_FLAGS_HAS_PASSWORD = 1 << 1, + G_URI_FLAGS_HAS_AUTH_PARAMS = 1 << 2, + G_URI_FLAGS_ENCODED = 1 << 3, + G_URI_FLAGS_NON_DNS = 1 << 4, + G_URI_FLAGS_ENCODED_QUERY = 1 << 5, + G_URI_FLAGS_ENCODED_PATH = 1 << 6, + G_URI_FLAGS_ENCODED_FRAGMENT = 1 << 7, + G_URI_FLAGS_SCHEME_NORMALIZE = 1 << 8, +} GUriFlags; + +typedef enum { + G_URI_HIDE_NONE = 0, + G_URI_HIDE_USERINFO = 1 << 0, + G_URI_HIDE_PASSWORD = 1 << 1, + G_URI_HIDE_AUTH_PARAMS = 1 << 2, + G_URI_HIDE_QUERY = 1 << 3, + G_URI_HIDE_FRAGMENT = 1 << 4, +} GUriHideFlags; + +typedef struct _GUri GUri; + +GUri * flatpak_g_uri_ref (GUri *uri); +void flatpak_g_uri_unref (GUri *uri); +GUri * flatpak_g_uri_parse (const gchar *uri_string, + GUriFlags flags, + GError **error); +GUri * flatpak_g_uri_parse_relative (GUri *base_uri, + const gchar *uri_ref, + GUriFlags flags, + GError **error); +char * flatpak_g_uri_to_string_partial (GUri *uri, + GUriHideFlags flags); +GUri * flatpak_g_uri_build (GUriFlags flags, + const gchar *scheme, + const gchar *userinfo, + const gchar *host, + gint port, + const gchar *path, + const gchar *query, + const gchar *fragment); +const gchar *flatpak_g_uri_get_scheme (GUri *uri); +const gchar *flatpak_g_uri_get_userinfo (GUri *uri); +const gchar *flatpak_g_uri_get_user (GUri *uri); +const gchar *flatpak_g_uri_get_password (GUri *uri); +const gchar *flatpak_g_uri_get_auth_params (GUri *uri); +const gchar *flatpak_g_uri_get_host (GUri *uri); +gint flatpak_g_uri_get_port (GUri *uri); +const gchar *flatpak_g_uri_get_path (GUri *uri); +const gchar *flatpak_g_uri_get_query (GUri *uri); +const gchar *flatpak_g_uri_get_fragment (GUri *uri); +GUriFlags flatpak_g_uri_get_flags (GUri *uri); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUri, flatpak_g_uri_unref) + +#define g_uri_ref flatpak_g_uri_ref +#define g_uri_unref flatpak_g_uri_unref +#define g_uri_parse flatpak_g_uri_parse +#define g_uri_parse_relative flatpak_g_uri_parse_relative +#define g_uri_to_string_partial flatpak_g_uri_to_string_partial +#define g_uri_build flatpak_g_uri_build +#define g_uri_get_scheme flatpak_g_uri_get_scheme +#define g_uri_get_userinfo flatpak_g_uri_get_userinfo +#define g_uri_get_user flatpak_g_uri_get_user +#define g_uri_get_password flatpak_g_uri_get_password +#define g_uri_get_auth_params flatpak_g_uri_get_auth_params +#define g_uri_get_host flatpak_g_uri_get_host +#define g_uri_get_port flatpak_g_uri_get_port +#define g_uri_get_path flatpak_g_uri_get_path +#define g_uri_get_query flatpak_g_uri_get_query +#define g_uri_get_fragment flatpak_g_uri_get_fragment +#define g_uri_get_flags flatpak_g_uri_get_flags + +#endif + + +#endif /* __FLATPAK_URI_PRIVATE_H__ */ diff --git a/common/flatpak-uri.c b/common/flatpak-uri.c new file mode 100644 index 00000000..7ec6606e --- /dev/null +++ b/common/flatpak-uri.c @@ -0,0 +1,1763 @@ +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: + * Copyright © 1995-1998 Free Software Foundation, Inc. + * Copyright © 2014-2019 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Alexander Larsson <alexl@redhat.com> + */ + +#include "config.h" + +#include <glib/gi18n-lib.h> + +#include "flatpak-uri-private.h" + +#if !GLIB_CHECK_VERSION (2, 66, 0) + +struct _GUri { + gchar *scheme; + gchar *userinfo; + gchar *host; + gint port; + gchar *path; + gchar *query; + gchar *fragment; + + gchar *user; + gchar *password; + gchar *auth_params; + + GUriFlags flags; + int ref_count; +}; + +GUri * +flatpak_g_uri_ref (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + g_atomic_int_inc (&uri->ref_count); + return uri; +} + +void +flatpak_g_uri_unref (GUri *uri) +{ + g_return_if_fail (uri != NULL); + + if (g_atomic_int_dec_and_test (&uri->ref_count)) + { + g_free (uri->scheme); + g_free (uri->userinfo); + g_free (uri->host); + g_free (uri->path); + g_free (uri->query); + g_free (uri->fragment); + g_free (uri->user); + g_free (uri->password); + g_free (uri->auth_params); + g_free (uri); + } +} + +static gboolean +flatpak_g_uri_char_is_unreserved (gchar ch) +{ + if (g_ascii_isalnum (ch)) + return TRUE; + return ch == '-' || ch == '.' || ch == '_' || ch == '~'; +} + +#define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10) +#define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2])) + +static gssize +uri_decoder (gchar **out, + const gchar *illegal_chars, + const gchar *start, + gsize length, + gboolean just_normalize, + gboolean www_form, + GUriFlags flags, + GError **error) +{ + gchar c; + GString *decoded; + const gchar *invalid, *s, *end; + gssize len; + + if (!(flags & G_URI_FLAGS_ENCODED)) + just_normalize = FALSE; + + decoded = g_string_sized_new (length + 1); + for (s = start, end = s + length; s < end; s++) + { + if (*s == '%') + { + if (s + 2 >= end || + !g_ascii_isxdigit (s[1]) || + !g_ascii_isxdigit (s[2])) + { + /* % followed by non-hex or the end of the string; this is an error */ + if (!(flags & G_URI_FLAGS_PARSE_RELAXED)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + /* xgettext: no-c-format */ + _("Invalid %-encoding in URI")); + g_string_free (decoded, TRUE); + return -1; + } + + /* In non-strict mode, just let it through; we *don't* + * fix it to "%25", since that might change the way that + * the URI's owner would interpret it. + */ + g_string_append_c (decoded, *s); + continue; + } + + c = HEXCHAR (s); + if (illegal_chars && strchr (illegal_chars, c)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Illegal character in URI")); + g_string_free (decoded, TRUE); + return -1; + } + if (just_normalize && !flatpak_g_uri_char_is_unreserved (c)) + { + /* Leave the % sequence there but normalize it. */ + g_string_append_c (decoded, *s); + g_string_append_c (decoded, g_ascii_toupper (s[1])); + g_string_append_c (decoded, g_ascii_toupper (s[2])); + s += 2; + } + else + { + g_string_append_c (decoded, c); + s += 2; + } + } + else if (www_form && *s == '+') + g_string_append_c (decoded, ' '); + /* Normalize any illegal characters. */ + else if (just_normalize && (!g_ascii_isgraph (*s))) + g_string_append_printf (decoded, "%%%02X", (guchar)*s); + else + g_string_append_c (decoded, *s); + } + + len = decoded->len; + g_assert (len >= 0); + + if (!(flags & G_URI_FLAGS_ENCODED) && + !g_utf8_validate (decoded->str, len, &invalid)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Non-UTF-8 characters in URI")); + g_string_free (decoded, TRUE); + return -1; + } + + if (out) + *out = g_string_free (decoded, FALSE); + else + g_string_free (decoded, TRUE); + + return len; +} + +static gboolean +uri_decode (gchar **out, + const gchar *illegal_chars, + const gchar *start, + gsize length, + gboolean www_form, + GUriFlags flags, + GError **error) +{ + return uri_decoder (out, illegal_chars, start, length, FALSE, www_form, flags, + error) != -1; +} + +static gboolean +uri_normalize (gchar **out, + const gchar *start, + gsize length, + GUriFlags flags, + GError **error) +{ + return uri_decoder (out, NULL, start, length, TRUE, FALSE, flags, + error) != -1; +} + +static gboolean +parse_ip_literal (const gchar *start, + gsize length, + GUriFlags flags, + gchar **out, + GError **error) +{ + gchar *pct, *zone_id = NULL; + gchar *addr = NULL; + gsize addr_length = 0; + gsize zone_id_length = 0; + gchar *decoded_zone_id = NULL; + + if (start[length - 1] != ']') + goto bad_ipv6_literal; + + /* Drop the square brackets */ + addr = g_strndup (start + 1, length - 2); + addr_length = length - 2; + + /* If there's an IPv6 scope ID, split out the zone. */ + pct = strchr (addr, '%'); + if (pct != NULL) + { + *pct = '\0'; + + if (addr_length - (pct - addr) >= 4 && + *(pct + 1) == '2' && *(pct + 2) == '5') + { + zone_id = pct + 3; + zone_id_length = addr_length - (zone_id - addr); + } + else if (flags & G_URI_FLAGS_PARSE_RELAXED && + addr_length - (pct - addr) >= 2) + { + zone_id = pct + 1; + zone_id_length = addr_length - (zone_id - addr); + } + else + goto bad_ipv6_literal; + + g_assert (zone_id_length >= 1); + } + + /* addr must be an IPv6 address */ + if (!g_hostname_is_ip_address (addr) || !strchr (addr, ':')) + goto bad_ipv6_literal; + + /* Zone ID must be valid. It can contain %-encoded characters. */ + if (zone_id != NULL && + !uri_decode (&decoded_zone_id, NULL, zone_id, zone_id_length, FALSE, + flags, NULL)) + goto bad_ipv6_literal; + + /* Success */ + if (out != NULL && decoded_zone_id != NULL) + *out = g_strconcat (addr, "%", decoded_zone_id, NULL); + else if (out != NULL) + *out = g_steal_pointer (&addr); + + g_free (addr); + g_free (decoded_zone_id); + + return TRUE; + +bad_ipv6_literal: + g_free (addr); + g_free (decoded_zone_id); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid IPv6 address ‘%.*s’ in URI"), + (gint)length, start); + + return FALSE; +} + +static gboolean +parse_host (const gchar *start, + gsize length, + GUriFlags flags, + gchar **out, + GError **error) +{ + gchar *decoded = NULL, *host; + gchar *addr = NULL; + + if (*start == '[') + { + if (!parse_ip_literal (start, length, flags, &host, error)) + return FALSE; + goto ok; + } + + if (g_ascii_isdigit (*start)) + { + addr = g_strndup (start, length); + if (g_hostname_is_ip_address (addr)) + { + host = addr; + goto ok; + } + g_free (addr); + } + + if (flags & G_URI_FLAGS_NON_DNS) + { + if (!uri_normalize (&decoded, start, length, flags, + error)) + return FALSE; + host = g_steal_pointer (&decoded); + goto ok; + } + + flags &= ~G_URI_FLAGS_ENCODED; + if (!uri_decode (&decoded, NULL, start, length, FALSE, flags, + error)) + return FALSE; + + /* You're not allowed to %-encode an IP address, so if it wasn't + * one before, it better not be one now. + */ + if (g_hostname_is_ip_address (decoded)) + { + g_free (decoded); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Illegal encoded IP address ‘%.*s’ in URI"), + (gint)length, start); + return FALSE; + } + + if (g_hostname_is_non_ascii (decoded)) + { + host = g_hostname_to_ascii (decoded); + if (host == NULL) + { + g_free (decoded); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Illegal internationalized hostname ‘%.*s’ in URI"), + (gint) length, start); + return FALSE; + } + } + else + { + host = g_steal_pointer (&decoded); + } + + ok: + if (out) + *out = g_steal_pointer (&host); + g_free (host); + g_free (decoded); + + return TRUE; +} + +static gboolean +parse_port (const gchar *start, + gsize length, + gint *out, + GError **error) +{ + gchar *end; + gulong parsed_port; + + /* strtoul() allows leading + or -, so we have to check this first. */ + if (!g_ascii_isdigit (*start)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Could not parse port ‘%.*s’ in URI"), + (gint)length, start); + return FALSE; + } + + /* We know that *(start + length) is either '\0' or a non-numeric + * character, so strtoul() won't scan beyond it. + */ + parsed_port = strtoul (start, &end, 10); + if (end != start + length) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Could not parse port ‘%.*s’ in URI"), + (gint)length, start); + return FALSE; + } + else if (parsed_port > 65535) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Port ‘%.*s’ in URI is out of range"), + (gint)length, start); + return FALSE; + } + + if (out) + *out = parsed_port; + return TRUE; +} + +static gboolean +parse_userinfo (const gchar *start, + gsize length, + GUriFlags flags, + gchar **user, + gchar **password, + gchar **auth_params, + GError **error) +{ + const gchar *user_end = NULL, *password_end = NULL, *auth_params_end; + + auth_params_end = start + length; + if (flags & G_URI_FLAGS_HAS_AUTH_PARAMS) + password_end = memchr (start, ';', auth_params_end - start); + if (!password_end) + password_end = auth_params_end; + if (flags & G_URI_FLAGS_HAS_PASSWORD) + user_end = memchr (start, ':', password_end - start); + if (!user_end) + user_end = password_end; + + if (!uri_normalize (user, start, user_end - start, flags, + error)) + return FALSE; + + if (*user_end == ':') + { + start = user_end + 1; + if (!uri_normalize (password, start, password_end - start, flags, + error)) + { + if (user) + g_clear_pointer (user, g_free); + return FALSE; + } + } + else if (password) + *password = NULL; + + if (*password_end == ';') + { + start = password_end + 1; + if (!uri_normalize (auth_params, start, auth_params_end - start, flags, + error)) + { + if (user) + g_clear_pointer (user, g_free); + if (password) + g_clear_pointer (password, g_free); + return FALSE; + } + } + else if (auth_params) + *auth_params = NULL; + + return TRUE; +} + +static gchar * +uri_cleanup (const gchar *uri_string) +{ + GString *copy; + const gchar *end; + + /* Skip leading whitespace */ + while (g_ascii_isspace (*uri_string)) + uri_string++; + + /* Ignore trailing whitespace */ + end = uri_string + strlen (uri_string); + while (end > uri_string && g_ascii_isspace (*(end - 1))) + end--; + + /* Copy the rest, encoding unencoded spaces and stripping other whitespace */ + copy = g_string_sized_new (end - uri_string); + while (uri_string < end) + { + if (*uri_string == ' ') + g_string_append (copy, "%20"); + else if (g_ascii_isspace (*uri_string)) + ; + else + g_string_append_c (copy, *uri_string); + uri_string++; + } + + return g_string_free (copy, FALSE); +} + +static gboolean +should_normalize_empty_path (const char *scheme) +{ + const char * const schemes[] = { "https", "http", "wss", "ws" }; + gsize i; + for (i = 0; i < G_N_ELEMENTS (schemes); ++i) + { + if (!strcmp (schemes[i], scheme)) + return TRUE; + } + return FALSE; +} + +static int +normalize_port (const char *scheme, + int port) +{ + const char *default_schemes[3] = { NULL }; + int i; + + switch (port) + { + case 21: + default_schemes[0] = "ftp"; + break; + case 80: + default_schemes[0] = "http"; + default_schemes[1] = "ws"; + break; + case 443: + default_schemes[0] = "https"; + default_schemes[1] = "wss"; + break; + default: + break; + } + + for (i = 0; default_schemes[i]; ++i) + { + if (!strcmp (scheme, default_schemes[i])) + return -1; + } + + return port; +} + +static int +default_scheme_port (const char *scheme) +{ + if (strcmp (scheme, "http") == 0 || strcmp (scheme, "ws") == 0) + return 80; + + if (strcmp (scheme, "https") == 0 || strcmp (scheme, "wss") == 0) + return 443; + + if (strcmp (scheme, "ftp") == 0) + return 21; + + return -1; +} + +static gboolean +flatpak_g_uri_split_internal (const gchar *uri_string, + GUriFlags flags, + gchar **scheme, + gchar **userinfo, + gchar **user, + gchar **password, + gchar **auth_params, + gchar **host, + gint *port, + gchar **path, + gchar **query, + gchar **fragment, + GError **error) +{ + const gchar *end, *colon, *at, *path_start, *semi, *question; + const gchar *p, *bracket, *hostend; + gchar *cleaned_uri_string = NULL; + gchar *normalized_scheme = NULL; + + if (scheme) + *scheme = NULL; + if (userinfo) + *userinfo = NULL; + if (user) + *user = NULL; + if (password) + *password = NULL; + if (auth_params) + *auth_params = NULL; + if (host) + *host = NULL; + if (port) + *port = -1; + if (path) + *path = NULL; + if (query) + *query = NULL; + if (fragment) + *fragment = NULL; + + if ((flags & G_URI_FLAGS_PARSE_RELAXED) && strpbrk (uri_string, " \t\n\r")) + { + cleaned_uri_string = uri_cleanup (uri_string); + uri_string = cleaned_uri_string; + } + + /* Find scheme */ + p = uri_string; + while (*p && (g_ascii_isalpha (*p) || + (p > uri_string && (g_ascii_isdigit (*p) || + *p == '.' || *p == '+' || *p == '-')))) + p++; + + if (p > uri_string && *p == ':') + { + normalized_scheme = g_ascii_strdown (uri_string, p - uri_string); + if (scheme) + *scheme = g_steal_pointer (&normalized_scheme); + p++; + } + else + { + if (scheme) + *scheme = NULL; + p = uri_string; + } + + /* Check for authority */ + if (strncmp (p, "//", 2) == 0) + { + p += 2; + + path_start = p + strcspn (p, "/?#"); + at = memchr (p, '@', path_start - p); + if (at) + { + if (flags & G_URI_FLAGS_PARSE_RELAXED) + { + gchar *next_at; + + /* Any "@"s in the userinfo must be %-encoded, but + * people get this wrong sometimes. Since "@"s in the + * hostname are unlikely (and also wrong anyway), assume + * that if there are extra "@"s, they belong in the + * userinfo. + */ + do + { + next_at = memchr (at + 1, '@', path_start - (at + 1)); + if (next_at) + at = next_at; + } + while (next_at); + } + + if (user || password || auth_params || + (flags & (G_URI_FLAGS_HAS_PASSWORD|G_URI_FLAGS_HAS_AUTH_PARAMS))) + { + if (!parse_userinfo (p, at - p, flags, + user, password, auth_params, + error)) + goto fail; + } + + if (!uri_normalize (userinfo, p, at - p, flags, + error)) + goto fail; + + p = at + 1; + } + + if (flags & G_URI_FLAGS_PARSE_RELAXED) + { + semi = strchr (p, ';'); + if (semi && semi < path_start) + { + /* Technically, semicolons are allowed in the "host" + * production, but no one ever does this, and some + * schemes mistakenly use semicolon as a delimiter + * marking the start of the path. We have to check this + * after checking for userinfo though, because a + * semicolon before the "@" must be part of the + * userinfo. + */ + path_start = semi; + } + } + + /* Find host and port. The host may be a bracket-delimited IPv6 + * address, in which case the colon delimiting the port must come + * (immediately) after the close bracket. + */ + if (*p == '[') + { + bracket = memchr (p, ']', path_start - p); + if (bracket && *(bracket + 1) == ':') + colon = bracket + 1; + else + colon = NULL; + } + else + colon = memchr (p, ':', path_start - p); + + hostend = colon ? colon : path_start; + if (!parse_host (p, hostend - p, flags, host, error)) + goto fail; + + if (colon && colon != path_start - 1) + { + p = colon + 1; + if (!parse_port (p, path_start - p, port, error)) + goto fail; + } + + p = path_start; + } + + /* Find fragment. */ + end = p + strcspn (p, "#"); + if (*end == '#') + { + if (!uri_normalize (fragment, end + 1, strlen (end + 1), + flags | (flags & G_URI_FLAGS_ENCODED_FRAGMENT ? G_URI_FLAGS_ENCODED : 0), + error)) + goto fail; + } + + /* Find query */ + question = memchr (p, '?', end - p); + if (question) + { + if (!uri_normalize (query, question + 1, end - (question + 1), + flags | (flags & G_URI_FLAGS_ENCODED_QUERY ? G_URI_FLAGS_ENCODED : 0), + error)) + goto fail; + end = question; + } + + if (!uri_normalize (path, p, end - p, + flags | (flags & G_URI_FLAGS_ENCODED_PATH ? G_URI_FLAGS_ENCODED : 0), + error)) + goto fail; + + /* Scheme-based normalization */ + if (flags & G_URI_FLAGS_SCHEME_NORMALIZE && ((scheme && *scheme) || normalized_scheme)) + { + const char *scheme_str = scheme && *scheme ? *scheme : normalized_scheme; + + if (should_normalize_empty_path (scheme_str) && path && !**path) + { + g_free (*path); + *path = g_strdup ("/"); + } + + if (port && *port == -1) + *port = default_scheme_port (scheme_str); + } + + g_free (normalized_scheme); + g_free (cleaned_uri_string); + return TRUE; + + fail: + if (scheme) + g_clear_pointer (scheme, g_free); + if (userinfo) + g_clear_pointer (userinfo, g_free); + if (host) + g_clear_pointer (host, g_free); + if (port) + *port = -1; + if (path) + g_clear_pointer (path, g_free); + if (query) + g_clear_pointer (query, g_free); + if (fragment) + g_clear_pointer (fragment, g_free); + + g_free (normalized_scheme); + g_free (cleaned_uri_string); + return FALSE; +} + + +/* Implements the "Remove Dot Segments" algorithm from section 5.2.4 of + * RFC 3986. + * + * See https://tools.ietf.org/html/rfc3986#section-5.2.4 + */ +static void +remove_dot_segments (gchar *path) +{ + /* The output can be written to the same buffer that the input + * is read from, as the output pointer is only ever increased + * when the input pointer is increased as well, and the input + * pointer is never decreased. */ + gchar *input = path; + gchar *output = path; + + if (!*path) + return; + + while (*input) + { + /* A. If the input buffer begins with a prefix of "../" or "./", + * then remove that prefix from the input buffer; otherwise, + */ + if (strncmp (input, "../", 3) == 0) + input += 3; + else if (strncmp (input, "./", 2) == 0) + input += 2; + + /* B. if the input buffer begins with a prefix of "/./" or "/.", + * where "." is a complete path segment, then replace that + * prefix with "/" in the input buffer; otherwise, + */ + else if (strncmp (input, "/./", 3) == 0) + input += 2; + else if (strcmp (input, "/.") == 0) + input[1] = '\0'; + + /* C. if the input buffer begins with a prefix of "/../" or "/..", + * where ".." is a complete path segment, then replace that + * prefix with "/" in the input buffer and remove the last + * segment and its preceding "/" (if any) from the output + * buffer; otherwise, + */ + else if (strncmp (input, "/../", 4) == 0) + { + input += 3; + if (output > path) + { + do + { + output--; + } + while (*output != '/' && output > path); + } + } + else if (strcmp (input, "/..") == 0) + { + input[1] = '\0'; + if (output > path) + { + do + { + output--; + } + while (*output != '/' && output > path); + } + } + + /* D. if the input buffer consists only of "." or "..", then remove + * that from the input buffer; otherwise, + */ + else if (strcmp (input, "..") == 0 || strcmp (input, ".") == 0) + input[0] = '\0'; + + /* E. move the first path segment in the input buffer to the end of + * the output buffer, including the initial "/" character (if + * any) and any subsequent characters up to, but not including, + * the next "/" character or the end of the input buffer. + */ + else + { + *output++ = *input++; + while (*input && *input != '/') + *output++ = *input++; + } + } + *output = '\0'; +} + +GUri * +flatpak_g_uri_parse (const gchar *uri_string, + GUriFlags flags, + GError **error) +{ + g_return_val_if_fail (uri_string != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + return flatpak_g_uri_parse_relative (NULL, uri_string, flags, error); +} + +GUri * +flatpak_g_uri_parse_relative (GUri *base_uri, + const gchar *uri_ref, + GUriFlags flags, + GError **error) +{ + GUri *uri = NULL; + + g_return_val_if_fail (uri_ref != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (base_uri == NULL || base_uri->scheme != NULL, NULL); + + /* Use GUri struct to construct the return value: there is no guarantee it is + * actually correct within the function body. */ + uri = g_new0 (GUri, 1); + uri->ref_count = 1; + uri->flags = flags; + + if (!flatpak_g_uri_split_internal (uri_ref, flags, + &uri->scheme, &uri->userinfo, + &uri->user, &uri->password, &uri->auth_params, + &uri->host, &uri->port, + &uri->path, &uri->query, &uri->fragment, + error)) + { + flatpak_g_uri_unref (uri); + return NULL; + } + + if (!uri->scheme && !base_uri) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("URI is not absolute, and no base URI was provided")); + flatpak_g_uri_unref (uri); + return NULL; + } + + if (base_uri) + { + /* This is section 5.2.2 of RFC 3986, except that we're doing + * it in place in @uri rather than copying from R to T. + * + * See https://tools.ietf.org/html/rfc3986#section-5.2.2 + */ + if (uri->scheme) + remove_dot_segments (uri->path); + else + { + uri->scheme = g_strdup (base_uri->scheme); + if (uri->host) + remove_dot_segments (uri->path); + else + { + if (!*uri->path) + { + g_free (uri->path); + uri->path = g_strdup (base_uri->path); + if (!uri->query) + uri->query = g_strdup (base_uri->query); + } + else + { + if (*uri->path == '/') + remove_dot_segments (uri->path); + else + { + gchar *newpath, *last; + + last = strrchr (base_uri->path, '/'); + if (last) + { + newpath = g_strdup_printf ("%.*s/%s", + (gint)(last - base_uri->path), + base_uri->path, + uri->path); + } + else + newpath = g_strdup_printf ("/%s", uri->path); + + g_free (uri->path); + uri->path = g_steal_pointer (&newpath); + + remove_dot_segments (uri->path); + } + } + + uri->userinfo = g_strdup (base_uri->userinfo); + uri->user = g_strdup (base_uri->user); + uri->password = g_strdup (base_uri->password); + uri->auth_params = g_strdup (base_uri->auth_params); + uri->host = g_strdup (base_uri->host); + uri->port = base_uri->port; + } + } + + /* Scheme normalization couldn't have been done earlier + * as the relative URI may not have had a scheme */ + if (flags & G_URI_FLAGS_SCHEME_NORMALIZE) + { + if (should_normalize_empty_path (uri->scheme) && !*uri->path) + { + g_free (uri->path); + uri->path = g_strdup ("/"); + } + + uri->port = normalize_port (uri->scheme, uri->port); + } + } + else + { + remove_dot_segments (uri->path); + } + + return g_steal_pointer (&uri); +} + +/* userinfo as a whole can contain sub-delims + ":", but split-out + * user can't contain ":" or ";", and split-out password can't contain + * ";". + */ +#define USERINFO_ALLOWED_CHARS G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO +#define USER_ALLOWED_CHARS "!$&'()*+,=" +#define PASSWORD_ALLOWED_CHARS "!$&'()*+,=:" +#define AUTH_PARAMS_ALLOWED_CHARS USERINFO_ALLOWED_CHARS +#define IP_ADDR_ALLOWED_CHARS ":" +#define HOST_ALLOWED_CHARS G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS +#define PATH_ALLOWED_CHARS G_URI_RESERVED_CHARS_ALLOWED_IN_PATH +#define QUERY_ALLOWED_CHARS G_URI_RESERVED_CHARS_ALLOWED_IN_PATH "?" +#define FRAGMENT_ALLOWED_CHARS G_URI_RESERVED_CHARS_ALLOWED_IN_PATH "?" + +static gchar * +flatpak_g_uri_join_internal (GUriFlags flags, + const gchar *scheme, + gboolean userinfo, + const gchar *user, + const gchar *password, + const gchar *auth_params, + const gchar *host, + gint port, + const gchar *path, + const gchar *query, + const gchar *fragment) +{ + gboolean encoded = (flags & G_URI_FLAGS_ENCODED); + GString *str; + char *normalized_scheme = NULL; + + /* Restrictions on path prefixes. See: + * https://tools.ietf.org/html/rfc3986#section-3 + */ + g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (host == NULL || (path[0] == '\0' || path[0] == '/'), NULL); + g_return_val_if_fail (host != NULL || (path[0] != '/' || path[1] != '/'), NULL); + + str = g_string_new (scheme); + if (scheme) + g_string_append_c (str, ':'); + + if (flags & G_URI_FLAGS_SCHEME_NORMALIZE && scheme && ((host && port != -1) || path[0] == '\0')) + normalized_scheme = g_ascii_strdown (scheme, -1); + + if (host) + { + g_string_append (str, "//"); + + if (user) + { + if (encoded) + g_string_append (str, user); + else + { + if (userinfo) + g_string_append_uri_escaped (str, user, USERINFO_ALLOWED_CHARS, TRUE); + else + /* Encode ':' and ';' regardless of whether we have a + * password or auth params, since it may be parsed later + * under the assumption that it does. + */ + g_string_append_uri_escaped (str, user, USER_ALLOWED_CHARS, TRUE); + } + + if (password) + { + g_string_append_c (str, ':'); + if (encoded) + g_string_append (str, password); + else + g_string_append_uri_escaped (str, password, + PASSWORD_ALLOWED_CHARS, TRUE); + } + + if (auth_params) + { + g_string_append_c (str, ';'); + if (encoded) + g_string_append (str, auth_params); + else + g_string_append_uri_escaped (str, auth_params, + AUTH_PARAMS_ALLOWED_CHARS, TRUE); + } + + g_string_append_c (str, '@'); + } + + if (strchr (host, ':') && g_hostname_is_ip_address (host)) + { + g_string_append_c (str, '['); + if (encoded) + g_string_append (str, host); + else + g_string_append_uri_escaped (str, host, IP_ADDR_ALLOWED_CHARS, TRUE); + g_string_append_c (str, ']'); + } + else + { + if (encoded) + g_string_append (str, host); + else + g_string_append_uri_escaped (str, host, HOST_ALLOWED_CHARS, TRUE); + } + + if (port != -1 && (!normalized_scheme || normalize_port (normalized_scheme, port) != -1)) + g_string_append_printf (str, ":%d", port); + } + + if (path[0] == '\0' && normalized_scheme && should_normalize_empty_path (normalized_scheme)) + g_string_append (str, "/"); + else if (encoded || flags & G_URI_FLAGS_ENCODED_PATH) + g_string_append (str, path); + else + g_string_append_uri_escaped (str, path, PATH_ALLOWED_CHARS, TRUE); + + g_free (normalized_scheme); + + if (query) + { + g_string_append_c (str, '?'); + if (encoded || flags & G_URI_FLAGS_ENCODED_QUERY) + g_string_append (str, query); + else + g_string_append_uri_escaped (str, query, QUERY_ALLOWED_CHARS, TRUE); + } + if (fragment) + { + g_string_append_c (str, '#'); + if (encoded || flags & G_URI_FLAGS_ENCODED_FRAGMENT) + g_string_append (str, fragment); + else + g_string_append_uri_escaped (str, fragment, FRAGMENT_ALLOWED_CHARS, TRUE); + } + + return g_string_free (str, FALSE); +} + +static gchar * +flatpak_g_uri_join (GUriFlags flags, + const gchar *scheme, + const gchar *userinfo, + const gchar *host, + gint port, + const gchar *path, + const gchar *query, + const gchar *fragment) +{ + g_return_val_if_fail (port >= -1 && port <= 65535, NULL); + g_return_val_if_fail (path != NULL, NULL); + + return flatpak_g_uri_join_internal (flags, + scheme, + TRUE, userinfo, NULL, NULL, + host, + port, + path, + query, + fragment); +} + +static gchar * +flatpak_g_uri_join_with_user (GUriFlags flags, + const gchar *scheme, + const gchar *user, + const gchar *password, + const gchar *auth_params, + const gchar *host, + gint port, + const gchar *path, + const gchar *query, + const gchar *fragment) +{ + g_return_val_if_fail (port >= -1 && port <= 65535, NULL); + g_return_val_if_fail (path != NULL, NULL); + + return flatpak_g_uri_join_internal (flags, + scheme, + FALSE, user, password, auth_params, + host, + port, + path, + query, + fragment); +} + +GUri * +flatpak_g_uri_build (GUriFlags flags, + const gchar *scheme, + const gchar *userinfo, + const gchar *host, + gint port, + const gchar *path, + const gchar *query, + const gchar *fragment) +{ + GUri *uri; + + g_return_val_if_fail (scheme != NULL, NULL); + g_return_val_if_fail (port >= -1 && port <= 65535, NULL); + g_return_val_if_fail (path != NULL, NULL); + + uri = g_new0 (GUri, 1); + uri->ref_count = 1; + uri->flags = flags; + uri->scheme = g_ascii_strdown (scheme, -1); + uri->userinfo = g_strdup (userinfo); + uri->host = g_strdup (host); + uri->port = port; + uri->path = g_strdup (path); + uri->query = g_strdup (query); + uri->fragment = g_strdup (fragment); + + return g_steal_pointer (&uri); +} + +gchar * +flatpak_g_uri_to_string_partial (GUri *uri, + GUriHideFlags flags) +{ + gboolean hide_user = (flags & G_URI_HIDE_USERINFO); + gboolean hide_password = (flags & (G_URI_HIDE_USERINFO | G_URI_HIDE_PASSWORD)); + gboolean hide_auth_params = (flags & (G_URI_HIDE_USERINFO | G_URI_HIDE_AUTH_PARAMS)); + gboolean hide_query = (flags & G_URI_HIDE_QUERY); + gboolean hide_fragment = (flags & G_URI_HIDE_FRAGMENT); + + g_return_val_if_fail (uri != NULL, NULL); + + if (uri->flags & (G_URI_FLAGS_HAS_PASSWORD | G_URI_FLAGS_HAS_AUTH_PARAMS)) + { + return flatpak_g_uri_join_with_user (uri->flags, + uri->scheme, + hide_user ? NULL : uri->user, + hide_password ? NULL : uri->password, + hide_auth_params ? NULL : uri->auth_params, + uri->host, + uri->port, + uri->path, + hide_query ? NULL : uri->query, + hide_fragment ? NULL : uri->fragment); + } + + return flatpak_g_uri_join (uri->flags, + uri->scheme, + hide_user ? NULL : uri->userinfo, + uri->host, + uri->port, + uri->path, + hide_query ? NULL : uri->query, + hide_fragment ? NULL : uri->fragment); +} + +const gchar * +flatpak_g_uri_get_scheme (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return uri->scheme; +} + +const gchar * +flatpak_g_uri_get_userinfo (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return uri->userinfo; +} + +const gchar * +flatpak_g_uri_get_user (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return uri->user; +} + +const gchar * +flatpak_g_uri_get_password (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return uri->password; +} + +const gchar * +flatpak_g_uri_get_auth_params (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return uri->auth_params; +} + +const gchar * +flatpak_g_uri_get_host (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return uri->host; +} + +gint +flatpak_g_uri_get_port (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, -1); + + if (uri->port == -1 && uri->flags & G_URI_FLAGS_SCHEME_NORMALIZE) + return default_scheme_port (uri->scheme); + + return uri->port; +} + +const gchar * +flatpak_g_uri_get_path (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return uri->path; +} + +const gchar * +flatpak_g_uri_get_query (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return uri->query; +} + +const gchar * +flatpak_g_uri_get_fragment (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + + return uri->fragment; +} + +GUriFlags +flatpak_g_uri_get_flags (GUri *uri) +{ + g_return_val_if_fail (uri != NULL, G_URI_FLAGS_NONE); + + return uri->flags; +} + +#endif /* GLIB_CHECK_VERSION (2, 66, 0) */ + + +static void +append_form_encoded (GString *str, const char *in) +{ + const unsigned char *s = (const unsigned char *)in; + + while (*s) + { + if (*s == ' ') + { + g_string_append_c (str, '+'); + s++; + } + else if (!g_ascii_isalnum (*s) && (*s != '-') && (*s != '_') + && (*s != '.')) + g_string_append_printf (str, "%%%02X", (int)*s++); + else + g_string_append_c (str, *s++); + } +} + +void +flatpak_uri_encode_query_arg (GString *str, + const char *key, + const char *value) +{ + if (str->len) + g_string_append_c (str, '&'); + append_form_encoded (str, key); + + g_string_append_c (str, '='); + append_form_encoded (str, value); +} + + +/* This is a simplified copy of soup_header_parse_param_list() to avoid a soup dependency */ + +static const char * +skip_lws (const char *s) +{ + while (g_ascii_isspace (*s)) + s++; + return s; +} + +static const char * +unskip_lws (const char *s, const char *start) +{ + while (s > start && g_ascii_isspace (*(s - 1))) + s--; + return s; +} + +static const char * +skip_delims (const char *s, char delim) +{ + /* The grammar allows for multiple delimiters */ + while (g_ascii_isspace (*s) || *s == delim) + s++; + return s; +} + +static const char * +skip_item (const char *s, char delim) +{ + gboolean quoted = FALSE; + const char *start = s; + + /* A list item ends at the last non-whitespace character + * before a delimiter which is not inside a quoted-string. Or + * at the end of the string. + */ + + while (*s) + { + if (*s == '"') + quoted = !quoted; + else if (quoted) + { + if (*s == '\\' && *(s + 1)) + s++; + } + else + { + if (*s == delim) + break; + } + s++; + } + + return unskip_lws (s, start); +} + +static GSList * +parse_list (const char *header, char delim) +{ + GSList *list = NULL; + const char *end; + + header = skip_delims (header, delim); + while (*header) + { + end = skip_item (header, delim); + list = g_slist_prepend (list, g_strndup (header, end - header)); + header = skip_delims (end, delim); + } + + return g_slist_reverse (list); +} + +static void +decode_quoted_string (char *quoted_string) +{ + char *src, *dst; + + src = quoted_string + 1; + dst = quoted_string; + while (*src && *src != '"') + { + if (*src == '\\' && *(src + 1)) + src++; + *dst++ = *src++; + } + *dst = '\0'; +} + +GHashTable * +flatpak_parse_http_header_param_list (const char *header) +{ + GHashTable *params; + GSList *list, *iter; + char *eq, *name_end, *value; + + params = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, g_free); + + list = parse_list (header, ','); + for (iter = list; iter; iter = iter->next) + { + g_autofree char *item = iter->data; + + eq = strchr (item, '='); + if (eq) + { + name_end = (char *)unskip_lws (eq, item); + if (name_end == item) + continue; + + *name_end = '\0'; + + value = (char *)skip_lws (eq + 1); + if (*value == '"') + decode_quoted_string (value); + } + else + value = NULL; + + g_autofree char *key = g_ascii_strdown (item, -1); + if (!g_hash_table_contains (params, key)) + g_hash_table_replace (params, g_steal_pointer (&key), g_strdup (value)); + } + + g_slist_free (list); + return params; +} + +/* Do not internationalize */ +static const char *const months[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +/* Do not internationalize */ +static const char *const days[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +char * +flatpak_format_http_date (GDateTime *date) +{ + g_autoptr(GDateTime) utcdate = g_date_time_to_utc (date); + g_autofree char *date_format = NULL; + + /* "Sun, 06 Nov 1994 08:49:37 GMT" */ + + date_format = g_strdup_printf ("%s, %%d %s %%Y %%T GMT", + days[g_date_time_get_day_of_week (utcdate) - 1], + months[g_date_time_get_month (utcdate) - 1]); + + return g_date_time_format (utcdate, (const char*)date_format); +} + + +static inline gboolean +parse_day (int *day, const char **date_string) +{ + char *end; + + *day = strtoul (*date_string, &end, 10); + if (end == (char *)*date_string) + return FALSE; + + while (*end == ' ' || *end == '-') + end++; + *date_string = end; + return TRUE; +} + +static inline gboolean +parse_month (int *month, const char **date_string) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (months); i++) + { + if (!g_ascii_strncasecmp (*date_string, months[i], 3)) + { + *month = i + 1; + *date_string += 3; + while (**date_string == ' ' || **date_string == '-') + (*date_string)++; + return TRUE; + } + } + + return FALSE; +} + +static inline gboolean +parse_year (int *year, const char **date_string) +{ + char *end; + + *year = strtoul (*date_string, &end, 10); + if (end == (char *)*date_string) + return FALSE; + + if (end == (char *)*date_string + 2) { + if (*year < 70) + *year += 2000; + else + *year += 1900; + } else if (end == (char *)*date_string + 3) + *year += 1900; + + while (*end == ' ' || *end == '-') + end++; + *date_string = end; + + return TRUE; +} + +static inline gboolean +parse_time (int *hour, int *minute, int *second, const char **date_string) +{ + char *p, *end; + + *hour = strtoul (*date_string, &end, 10); + if (end == (char *)*date_string || *end++ != ':') + return FALSE; + p = end; + *minute = strtoul (p, &end, 10); + if (end == p || *end++ != ':') + return FALSE; + p = end; + *second = strtoul (p, &end, 10); + if (end == p) + return FALSE; + p = end; + + while (*p == ' ') + p++; + *date_string = p; + + return TRUE; +} + +static inline GTimeZone * +time_zone_new_offset (gint32 offset) +{ +#if GLIB_CHECK_VERSION (2, 58, 0) + return g_time_zone_new_offset (offset); +#else + g_autofree char *id = NULL; + gint hours, minutes; + gint seconds = offset; + GTimeZone *tz; + char sign = '+'; + + if (seconds == 0) + return g_time_zone_new_utc (); + + if (seconds < 0) + { + seconds = -seconds; + sign = '-'; + } + + hours = seconds / 3600; + seconds = seconds % 3600; + minutes = seconds / 60; + seconds = seconds % 60; + + id = g_strdup_printf ("%c%02d:%02d:%02d", sign, hours, minutes, seconds); + tz = g_time_zone_new (id); + /* If this assertion fails, we'll log a critical but still return tz, + * which is documented to be UTC if the time zone could not be parsed */ + g_return_val_if_fail (g_time_zone_get_offset (tz, 0) == offset, tz); + return tz; +#endif +} + +static inline gboolean +parse_timezone (GTimeZone **timezone_out, const char **date_string) +{ + gint32 offset_minutes; + gboolean utc; + + if (!**date_string) + { + utc = FALSE; + offset_minutes = 0; + } + else if (**date_string == '+' || **date_string == '-') + { + gulong val; + int sign = (**date_string == '+') ? 1 : -1; + val = strtoul (*date_string + 1, (char **)date_string, 10); + if (**date_string == ':') + val = 60 * val + strtoul (*date_string + 1, (char **)date_string, 10); + else + val = 60 * (val / 100) + (val % 100); + offset_minutes = sign * val; + utc = (sign == -1) && !val; + } + else if (**date_string == 'Z') + { + offset_minutes = 0; + utc = TRUE; + (*date_string)++; + } + else if (!strcmp (*date_string, "GMT") || + !strcmp (*date_string, "UTC")) + { + offset_minutes = 0; + utc = TRUE; + (*date_string) += 3; + } + else if (strchr ("ECMP", **date_string) && + ((*date_string)[1] == 'D' || (*date_string)[1] == 'S') && + (*date_string)[2] == 'T') { + offset_minutes = -60 * (5 * strcspn ("ECMP", *date_string)); + if ((*date_string)[1] == 'D') + offset_minutes += 60; + utc = FALSE; + } + else + return FALSE; + + if (utc) + *timezone_out = g_time_zone_new_utc (); + else + *timezone_out = time_zone_new_offset (offset_minutes * 60); + + return TRUE; +} + +GDateTime * +flatpak_parse_http_time (const char *date_string) +{ + int month, day, year, hour, minute, second; + g_autoptr(GTimeZone) tz = NULL; + + g_return_val_if_fail (date_string != NULL, NULL); + + while (g_ascii_isspace (*date_string)) + date_string++; + + /* If it starts with a word, it must be a weekday, which we skip */ + if (g_ascii_isalpha (*date_string)) + { + while (g_ascii_isalpha (*date_string)) + date_string++; + if (*date_string == ',') + date_string++; + while (g_ascii_isspace (*date_string)) + date_string++; + } + + /* If there's now another word, this must be an asctime-date */ + if (g_ascii_isalpha (*date_string)) + { + /* (Sun) Nov 6 08:49:37 1994 */ + if (!parse_month (&month, &date_string) || + !parse_day (&day, &date_string) || + !parse_time (&hour, &minute, &second, &date_string) || + !parse_year (&year, &date_string)) + return NULL; + + /* There shouldn't be a timezone, but check anyway */ + parse_timezone (&tz, &date_string); + } + else + { + /* Non-asctime date, so some variation of + * (Sun,) 06 Nov 1994 08:49:37 GMT + */ + if (!parse_day (&day, &date_string) || + !parse_month (&month, &date_string) || + !parse_year (&year, &date_string) || + !parse_time (&hour, &minute, &second, &date_string)) + return NULL; + + /* This time there *should* be a timezone, but we + * survive if there isn't. + */ + parse_timezone (&tz, &date_string); + } + + if (!tz) + tz = g_time_zone_new_utc (); + + return g_date_time_new (tz, year, month, day, hour, minute, second); +} diff --git a/common/flatpak-utils-base.c b/common/flatpak-utils-base.c index d5f19e81..ef91d1b1 100644 --- a/common/flatpak-utils-base.c +++ b/common/flatpak-utils-base.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2019 Red Hat, Inc * * This program is free software; you can redistribute it and/or diff --git a/common/flatpak-utils-http-private.h b/common/flatpak-utils-http-private.h index f1319f0f..2c89ba40 100644 --- a/common/flatpak-utils-http-private.h +++ b/common/flatpak-utils-http-private.h @@ -23,8 +23,6 @@ #include <string.h> -#include <libsoup/soup.h> - typedef enum { FLATPAK_HTTP_ERROR_NOT_CHANGED = 0, FLATPAK_HTTP_ERROR_UNAUTHORIZED = 1, @@ -34,19 +32,37 @@ typedef enum { GQuark flatpak_http_error_quark (void); +typedef struct FlatpakHttpSession FlatpakHttpSession; + +FlatpakHttpSession* flatpak_create_http_session (const char *user_agent); +void flatpak_http_session_free (FlatpakHttpSession* http_session); -SoupSession * flatpak_create_soup_session (const char *user_agent); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(FlatpakHttpSession, flatpak_http_session_free) typedef enum { FLATPAK_HTTP_FLAGS_NONE = 0, FLATPAK_HTTP_FLAGS_ACCEPT_OCI = 1 << 0, - FLATPAK_HTTP_FLAGS_STORE_COMPRESSED = 2 << 0, + FLATPAK_HTTP_FLAGS_STORE_COMPRESSED = 1 << 1, + FLATPAK_HTTP_FLAGS_NOCHECK_STATUS = 1 << 2, + FLATPAK_HTTP_FLAGS_HEAD = 1 << 3, } FlatpakHTTPFlags; typedef void (*FlatpakLoadUriProgress) (guint64 downloaded_bytes, gpointer user_data); -GBytes * flatpak_load_uri (SoupSession *soup_session, +GBytes * flatpak_load_uri_full (FlatpakHttpSession *http_session, + const char *uri, + FlatpakHTTPFlags flags, + const char *auth, + const char *token, + FlatpakLoadUriProgress progress, + gpointer user_data, + int *out_status, + char **out_content_type, + char **out_www_authenticate, + GCancellable *cancellable, + GError **error); +GBytes * flatpak_load_uri (FlatpakHttpSession *http_session, const char *uri, FlatpakHTTPFlags flags, const char *token, @@ -55,7 +71,7 @@ GBytes * flatpak_load_uri (SoupSession *soup_session, char **out_content_type, GCancellable *cancellable, GError **error); -gboolean flatpak_download_http_uri (SoupSession *soup_session, +gboolean flatpak_download_http_uri (FlatpakHttpSession *http_session, const char *uri, FlatpakHTTPFlags flags, GOutputStream *out, @@ -64,7 +80,7 @@ gboolean flatpak_download_http_uri (SoupSession *soup_session, gpointer user_data, GCancellable *cancellable, GError **error); -gboolean flatpak_cache_http_uri (SoupSession *soup_session, +gboolean flatpak_cache_http_uri (FlatpakHttpSession *http_session, const char *uri, FlatpakHTTPFlags flags, int dest_dfd, diff --git a/common/flatpak-utils-http.c b/common/flatpak-utils-http.c index 6f2421ba..e180345a 100644 --- a/common/flatpak-utils-http.c +++ b/common/flatpak-utils-http.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 2018 Red Hat, Inc * * This program is free software; you can redistribute it and/or @@ -18,21 +18,64 @@ * Alexander Larsson <alexl@redhat.com> */ +#include "config.h" + +#include <gio/gio.h> +#include <glib-unix.h> #include "flatpak-utils-http-private.h" +#include "flatpak-uri-private.h" #include "flatpak-oci-registry-private.h" #include <gio/gunixoutputstream.h> -#include <libsoup/soup.h> #include "libglnx.h" #include <sys/types.h> #include <sys/xattr.h> +#if defined(HAVE_CURL) + +#include <curl/curl.h> + +/* These macros came from 7.43.0, but we want to check + * for versions a bit earlier than that (to work on CentOS 7), + * so define them here if we're using an older version. + */ +#ifndef CURL_VERSION_BITS +#define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|z) +#endif +#ifndef CURL_AT_LEAST_VERSION +#define CURL_AT_LEAST_VERSION(x,y,z) (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z)) +#endif + +#elif defined(HAVE_SOUP) + +#include <libsoup/soup.h> + +#if !defined(SOUP_AUTOCLEANUPS_H) && !defined(__SOUP_AUTOCLEANUPS_H__) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupSession, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupMessage, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupRequest, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupRequestHTTP, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupURI, soup_uri_free) +#endif + +#else + +# error "No HTTP backend enabled" + +#endif + + +#define FLATPAK_HTTP_TIMEOUT_SECS 60 + /* copied from libostree */ #define DEFAULT_N_NETWORK_RETRIES 5 G_DEFINE_QUARK (flatpak_http_error, flatpak_http_error) +/* Information about the cache status of a file. + Encoded in an xattr on the cached file, or a file on the side if xattrs don't work. +*/ typedef struct { char *uri; @@ -46,290 +89,516 @@ typedef struct GMainContext *context; gboolean done; GError *error; - gboolean store_compressed; + + /* Input args */ + + FlatpakHTTPFlags flags; + const char *auth; + const char *token; + FlatpakLoadUriProgress progress; + GCancellable *cancellable; + gpointer user_data; + CacheHttpData *cache_data; + + /* Output from the request, set even on http server errors */ + + guint64 downloaded_bytes; + int status; + char *hdr_content_type; + char *hdr_www_authenticate; + char *hdr_etag; + char *hdr_last_modified; + char *hdr_cache_control; + char *hdr_expires; + char *hdr_content_encoding; + + /* Data destination */ GOutputStream *out; /*or */ GString *content; /* or */ GLnxTmpfile *out_tmpfile; int out_tmpfile_parent_dfd; - guint64 downloaded_bytes; + /* Used during operation */ + char buffer[16 * 1024]; - FlatpakLoadUriProgress progress; - GCancellable *cancellable; - gpointer user_data; guint64 last_progress_time; - CacheHttpData *cache_data; - char **content_type_out; + gboolean store_compressed; + } LoadUriData; -#define CACHE_HTTP_XATTR "user.flatpak.http" -#define CACHE_HTTP_SUFFIX ".flatpak.http" -#define CACHE_HTTP_TYPE "(sstt)" +static void +clear_load_uri_data_headers (LoadUriData *data) +{ + g_clear_pointer (&data->hdr_content_type, g_free); + g_clear_pointer (&data->hdr_www_authenticate, g_free); + g_clear_pointer (&data->hdr_etag, g_free); + g_clear_pointer (&data->hdr_last_modified, g_free); + g_clear_pointer (&data->hdr_last_modified, g_free); + g_clear_pointer (&data->hdr_cache_control, g_free); + g_clear_pointer (&data->hdr_expires, g_free); + g_clear_pointer (&data->hdr_content_encoding, g_free); +} +/* Reset between requests retries */ static void -clear_cache_http_data (CacheHttpData *data, - gboolean clear_uri) +reset_load_uri_data (LoadUriData *data) { - if (clear_uri) - g_clear_pointer (&data->uri, g_free); - g_clear_pointer (&data->etag, g_free); - data->last_modified = 0; - data->expires = 0; + g_clear_error (&data->error); + data->status = 0; + data->downloaded_bytes = 0; + if (data->content) + g_string_set_size (data->content, 0); + + clear_load_uri_data_headers (data); + + if (data->out_tmpfile) + { + glnx_tmpfile_clear (data->out_tmpfile); + g_clear_pointer (&data->out, g_object_unref); + } + + /* Reset the progress */ + if (data->progress) + data->progress (0, data->user_data); } +/* Free allocated data at end of full repeated download */ static void -free_cache_http_data (CacheHttpData *data) +clear_load_uri_data (LoadUriData *data) { - clear_cache_http_data (data, TRUE); - g_free (data); + if (data->content) + { + g_string_free (data->content, TRUE); + data->content = NULL; + } + + g_clear_error (&data->error); + + clear_load_uri_data_headers (data); } -G_DEFINE_AUTOPTR_CLEANUP_FUNC (CacheHttpData, free_cache_http_data) +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(LoadUriData, clear_load_uri_data) -static GBytes * -serialize_cache_http_data (CacheHttpData * data) +static gboolean +check_http_status (guint status_code, + GError **error) { - g_autoptr(GVariant) cache_variant = NULL; + GQuark domain; + int code; - cache_variant = g_variant_ref_sink (g_variant_new (CACHE_HTTP_TYPE, - data->uri, - data->etag ? data->etag : "", - data->last_modified, - data->expires)); - if (G_BYTE_ORDER != G_BIG_ENDIAN) + if (status_code >= 200 && status_code < 300) + return TRUE; + + switch (status_code) { - g_autoptr(GVariant) tmp_variant = cache_variant; - cache_variant = g_variant_byteswap (tmp_variant); + case 304: /* Not Modified */ + domain = FLATPAK_HTTP_ERROR; + code = FLATPAK_HTTP_ERROR_NOT_CHANGED; + break; + + case 401: /* Unauthorized */ + domain = FLATPAK_HTTP_ERROR; + code = FLATPAK_HTTP_ERROR_UNAUTHORIZED; + break; + + case 403: /* Forbidden */ + case 404: /* Not found */ + case 410: /* Gone */ + domain = G_IO_ERROR; + code = G_IO_ERROR_NOT_FOUND; + break; + + case 408: /* Request Timeout */ + domain = G_IO_ERROR; + code = G_IO_ERROR_TIMED_OUT; + break; + + case 500: /* Internal Server Error */ + /* The server did return something, but it was useless to us, so that’s basically equivalent to not returning */ + domain = G_IO_ERROR; + code = G_IO_ERROR_HOST_UNREACHABLE; + break; + + default: + domain = G_IO_ERROR; + code = G_IO_ERROR_FAILED; } - return g_variant_get_data_as_bytes (cache_variant); + g_set_error (error, domain, code, + "Server returned status %u", + status_code); + return FALSE; } +#if defined(HAVE_CURL) + +/************************************************************************ + * Curl implementation * + ************************************************************************/ + +typedef struct curl_slist auto_curl_slist; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (auto_curl_slist, curl_slist_free_all) + +struct FlatpakHttpSession { + CURL *curl; + GMutex lock; +}; + static void -deserialize_cache_http_data (CacheHttpData *data, - GBytes *bytes) +check_header(char **value_out, + const char *header, + char *buffer, + size_t realsize) { - g_autoptr(GVariant) cache_variant = NULL; + size_t hlen = strlen (header); - cache_variant = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE (CACHE_HTTP_TYPE), - bytes, - FALSE)); - if (G_BYTE_ORDER != G_BIG_ENDIAN) + if (realsize < hlen + 1) + return; + + if (!g_ascii_strncasecmp(buffer, header, hlen) == 0 || + buffer[hlen] != ':') + return; + + buffer += hlen + 1; + realsize -= hlen + 1; + + while (realsize > 0 && g_ascii_isspace (*buffer)) { - g_autoptr(GVariant) tmp_variant = cache_variant; - cache_variant = g_variant_byteswap (tmp_variant); + buffer++; + realsize--; } - g_variant_get (cache_variant, - CACHE_HTTP_TYPE, - &data->uri, - &data->etag, - &data->last_modified, - &data->expires); + while (realsize > 0 && g_ascii_isspace (buffer[realsize-1])) + realsize--; + + g_free (*value_out); /* Use the last header */ + *value_out = g_strndup (buffer, realsize); } -static CacheHttpData * -load_cache_http_data (int dfd, - char *name, - gboolean *no_xattr, - GCancellable *cancellable, - GError **error) +static size_t +_header_cb (char *buffer, + size_t size, + size_t nitems, + void *userdata) { - g_autoptr(CacheHttpData) data = NULL; - g_autoptr(GBytes) cache_bytes = glnx_lgetxattrat (dfd, name, - CACHE_HTTP_XATTR, - error); - if (cache_bytes == NULL) - { - if (errno == ENOTSUP) - { - g_autofree char *cache_file = NULL; - glnx_autofd int fd = -1; + size_t realsize = size * nitems; + LoadUriData *data = (LoadUriData *)userdata; - g_clear_error (error); - *no_xattr = TRUE; + check_header(&data->hdr_content_type, "content-type", buffer, realsize); + check_header(&data->hdr_www_authenticate, "WWW-Authenticate", buffer, realsize); - cache_file = g_strconcat (name, CACHE_HTTP_SUFFIX, NULL); + check_header(&data->hdr_etag, "ETag", buffer, realsize); + check_header(&data->hdr_last_modified, "Last-Modified", buffer, realsize); + check_header(&data->hdr_cache_control, "Cache-Control", buffer, realsize); + check_header(&data->hdr_expires, "Expires", buffer, realsize); + check_header(&data->hdr_content_encoding, "Content-Encoding", buffer, realsize); - if (!glnx_openat_rdonly (dfd, cache_file, FALSE, - &fd, error)) - return FALSE; + return realsize; +} - cache_bytes = glnx_fd_readall_bytes (fd, cancellable, error); - if (!cache_bytes) - return NULL; +static size_t +_write_cb (void *content_data, + size_t size, + size_t nmemb, + void *userp) +{ + size_t realsize = size * nmemb; + LoadUriData *data = (LoadUriData *)userp; + gsize n_written = 0; + + /* If first write to tmpfile, initiate if needed */ + if (data->content == NULL && data->out == NULL && + data->out_tmpfile != NULL) + { + g_autoptr(GOutputStream) out = NULL; + g_autoptr(GError) tmp_error = NULL; + + if (!glnx_open_tmpfile_linkable_at (data->out_tmpfile_parent_dfd, ".", + O_WRONLY, data->out_tmpfile, + &tmp_error)) + { + g_warning ("Failed to open http tmpfile: %s\n", tmp_error->message); + return 0; /* This short read will make curl report an error */ } - else if (errno == ENOENT || errno == ENODATA) + + out = g_unix_output_stream_new (data->out_tmpfile->fd, FALSE); + if (data->store_compressed && + g_strcmp0 (data->hdr_content_encoding, "gzip") != 0) { - g_clear_error (error); - return g_new0 (CacheHttpData, 1); + g_autoptr(GZlibCompressor) compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1); + data->out = g_converter_output_stream_new (out, G_CONVERTER (compressor)); } else { - return NULL; + data->out = g_steal_pointer (&out); } } + if (data->content) + { + g_string_append_len (data->content, content_data, realsize); + n_written = realsize; + } + else if (data->out) + { + /* We ignore the error here, but reporting a short read will make curl report the error */ + g_output_stream_write_all (data->out, content_data, realsize, + &n_written, NULL, NULL); + } - data = g_new0 (CacheHttpData, 1); - deserialize_cache_http_data (data, cache_bytes); - return g_steal_pointer (&data); + data->downloaded_bytes += realsize; + + if (g_get_monotonic_time () - data->last_progress_time > 1 * G_USEC_PER_SEC) + { + if (data->progress) + data->progress (data->downloaded_bytes, data->user_data); + data->last_progress_time = g_get_monotonic_time (); + } + + return realsize; } -static void -set_cache_http_data_from_headers (CacheHttpData *data, - SoupMessage *msg) +FlatpakHttpSession * +flatpak_create_http_session (const char *user_agent) { - const char *etag = soup_message_headers_get_one (msg->response_headers, "ETag"); - const char *last_modified = soup_message_headers_get_one (msg->response_headers, "Last-Modified"); - const char *cache_control = soup_message_headers_get_list (msg->response_headers, "Cache-Control"); - const char *expires = soup_message_headers_get_list (msg->response_headers, "Expires"); - gboolean expires_computed = FALSE; + FlatpakHttpSession *session = g_new0 (FlatpakHttpSession, 1); + CURLcode rc; + CURL *curl; - /* The original HTTP 1/1 specification only required sending the ETag header in a 304 - * response, and implied that a cache might need to save the old Cache-Control - * values. The updated RFC 7232 from 2014 requires sending Cache-Control, ETags, and - * Expire if they would have been sent in the original 200 response, and recommends - * sending Last-Modified for requests without an etag. Since sending these headers was - * apparently normal previously, for simplicity we assume the RFC 7232 behavior and start - * from scratch for a 304 response. + session->curl = curl = curl_easy_init(); + g_assert (session->curl != NULL); + + g_mutex_init (&session->lock); + + curl_easy_setopt (curl, CURLOPT_USERAGENT, user_agent); + rc = curl_easy_setopt (curl, CURLOPT_PROTOCOLS, (long)(CURLPROTO_HTTP | CURLPROTO_HTTPS)); + g_assert_cmpint (rc, ==, CURLM_OK); + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + + /* Note: curl automatically respects the http_proxy env var */ + + if (g_getenv ("OSTREE_DEBUG_HTTP")) + curl_easy_setopt (curl, CURLOPT_VERBOSE, 1L); + + /* Picked the current version in F25 as of 20170127, since + * there are numerous HTTP/2 fixes since the original version in + * libcurl 7.43.0. */ - clear_cache_http_data (data, FALSE); +#if CURL_AT_LEAST_VERSION(7, 51, 0) + if ((curl_version_info (CURLVERSION_NOW))->features & CURL_VERSION_HTTP2) { + rc = curl_easy_setopt (curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + g_assert_cmpint (rc, ==, CURLM_OK); + } +#endif + /* https://github.com/curl/curl/blob/curl-7_53_0/docs/examples/http2-download.c */ +#if (CURLPIPE_MULTIPLEX > 0) + /* wait for pipe connection to confirm */ + rc = curl_easy_setopt (curl, CURLOPT_PIPEWAIT, 1L); + g_assert_cmpint (rc, ==, CURLM_OK); +#endif - if (etag && *etag) + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _write_cb); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, _header_cb); + + curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, (long)FLATPAK_HTTP_TIMEOUT_SECS); + + return session; +} + +void +flatpak_http_session_free (FlatpakHttpSession* session) +{ + g_mutex_lock (&session->lock); + curl_easy_cleanup (session->curl); + g_mutex_unlock (&session->lock); + g_mutex_clear (&session->lock); + g_free (session); +} + +static void +set_error_from_curl (GError **error, + const char *uri, + CURLcode res) +{ + GQuark domain = G_IO_ERROR; + int code; + + switch (res) { - data->etag = g_strdup (etag); + case CURLE_COULDNT_CONNECT: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_COULDNT_RESOLVE_PROXY: + code = G_IO_ERROR_HOST_NOT_FOUND; + break; + case CURLE_OPERATION_TIMEDOUT: + code = G_IO_ERROR_TIMED_OUT; + break; + default: + code = G_IO_ERROR_FAILED; } - else if (last_modified && *last_modified) + + g_set_error (error, domain, code, + "While fetching %s: [%u] %s", uri, res, + curl_easy_strerror (res)); +} + +static gboolean +flatpak_download_http_uri_once (FlatpakHttpSession *session, + LoadUriData *data, + const char *uri, + GError **error) +{ + CURLcode res; + g_autofree char *auth_header = NULL; + g_autofree char *cache_header = NULL; + g_autoptr(auto_curl_slist) header_list = NULL; + g_autoptr(GMutexLocker) curl_lock = g_mutex_locker_new (&session->lock); + long response; + CURL *curl = session->curl; + + g_info ("Loading %s using curl", uri); + + curl_easy_setopt (curl, CURLOPT_URL, uri); + curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void *)data); + curl_easy_setopt (curl, CURLOPT_HEADERDATA, (void *)data); + + if (data->flags & FLATPAK_HTTP_FLAGS_HEAD) + curl_easy_setopt (curl, CURLOPT_NOBODY, 1L); + else + curl_easy_setopt (curl, CURLOPT_HTTPGET, 1L); + + if (data->flags & FLATPAK_HTTP_FLAGS_ACCEPT_OCI) + header_list = curl_slist_append (header_list, + "Accept: " FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFEST ", " FLATPAK_DOCKER_MEDIA_TYPE_IMAGE_MANIFEST2 ", " FLATPAK_OCI_MEDIA_TYPE_IMAGE_INDEX); + + if (data->auth) + auth_header = g_strdup_printf ("Authorization: Basic %s", data->auth); + else if (data->token) + auth_header = g_strdup_printf ("Authorization: Bearer %s", data->token); + if (auth_header) + header_list = curl_slist_append (header_list, auth_header); + + if (data->cache_data) { - SoupDate *date = soup_date_new_from_string (last_modified); - if (date) + CacheHttpData *cache_data = data->cache_data; + + if (cache_data->etag && cache_data->etag[0]) + cache_header = g_strdup_printf ("If-None-Match: %s", cache_data->etag); + else if (cache_data->last_modified != 0) { - data->last_modified = soup_date_to_time_t (date); - soup_date_free (date); + g_autoptr(GDateTime) date = g_date_time_new_from_unix_utc (cache_data->last_modified); + g_autofree char *date_str = flatpak_format_http_date (date); + cache_header = g_strdup_printf ("If-Modified-Since: %s", date_str); } + if (cache_header) + header_list = curl_slist_append (header_list, cache_header); } - if (cache_control && *cache_control) + curl_easy_setopt (curl, CURLOPT_HTTPHEADER, header_list); + + if (data->flags & FLATPAK_HTTP_FLAGS_STORE_COMPRESSED) { - g_autoptr(GHashTable) params = soup_header_parse_param_list (cache_control); - GHashTableIter iter; - gpointer key, value; + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip"); + curl_easy_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, 0L); + data->store_compressed = TRUE; + } + else + { + /* enable all supported built-in compressions */ + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(curl, CURLOPT_HTTP_CONTENT_DECODING, 1L); + data->store_compressed = FALSE; + } - g_hash_table_iter_init (&iter, params); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - if (g_strcmp0 (key, "max-age") == 0) - { - char *end; + res = curl_easy_perform (session->curl); - char *max_age = value; - int max_age_sec = g_ascii_strtoll (max_age, &end, 10); - if (*max_age != '\0' && *end == '\0') - { - GTimeVal now; - g_get_current_time (&now); - data->expires = now.tv_sec + max_age_sec; - expires_computed = TRUE; - } - } - else if (g_strcmp0 (key, "no-cache") == 0) - { - data->expires = 0; - expires_computed = TRUE; - } - } - } + curl_easy_setopt (session->curl, CURLOPT_HTTPHEADER, NULL); /* Don't point to freed list */ - if (!expires_computed && expires && *expires) + if (res != CURLE_OK) { - SoupDate *date = soup_date_new_from_string (expires); - if (date) - { - data->expires = soup_date_to_time_t (date); - soup_date_free (date); - expires_computed = TRUE; - } + set_error_from_curl (error, uri, res); + + /* Make sure we clear the tmpfile stream we possible created during the request */ + if (data->out_tmpfile && data->out) + g_clear_pointer (&data->out, g_object_unref); + + return FALSE; } - if (!expires_computed) + if (data->out_tmpfile && data->out) { - /* If nothing implies an expires time, use 30 minutes. Browsers use - * 0.1 * (Date - Last-Modified), but it's clearly appropriate here, and - * better if server's send a value. - */ - GTimeVal now; - g_get_current_time (&now); - data->expires = now.tv_sec + 1800; + /* Flush the writes */ + if (!g_output_stream_close (data->out, data->cancellable, error)) + return FALSE; + + g_clear_pointer (&data->out, g_object_unref); } -} -static gboolean -save_cache_http_data_xattr (int fd, - GBytes *bytes, - GError **error) -{ - if (TEMP_FAILURE_RETRY (fsetxattr (fd, (char *) CACHE_HTTP_XATTR, - g_bytes_get_data (bytes, NULL), - g_bytes_get_size (bytes), - 0)) < 0) - return glnx_throw_errno_prefix (error, "fsetxattr"); + if (data->progress) + data->progress (data->downloaded_bytes, data->user_data); - return TRUE; -} + curl_easy_getinfo (session->curl, CURLINFO_RESPONSE_CODE, &response); -static gboolean -save_cache_http_data_fallback (int fd, - GBytes *bytes, - GError **error) -{ - if (glnx_loop_write (fd, - g_bytes_get_data (bytes, NULL), - g_bytes_get_size (bytes)) < 0) - return glnx_throw_errno_prefix (error, "write"); + data->status = response; + + if ((data->flags & FLATPAK_HTTP_FLAGS_NOCHECK_STATUS) == 0 && + !check_http_status (data->status, error)) + return FALSE; + + g_info ("Received %" G_GUINT64_FORMAT " bytes", data->downloaded_bytes); + + /* This is not really needed, but the auto-pointer confuses some compilers in the CI */ + g_clear_pointer (&curl_lock, g_mutex_locker_free); return TRUE; } +#endif /* HAVE_CURL */ + +#if defined(HAVE_SOUP) + +/************************************************************************ + * Soup implementation * + ***********************************************************************/ + static gboolean -save_cache_http_data_to_file (int dfd, - char *name, - GBytes *bytes, - gboolean no_xattr, - GCancellable *cancellable, - GError **error) +check_soup_transfer_error (SoupMessage *msg, GError **error) { - glnx_autofd int fd = -1; - g_autofree char *fallback_name = NULL; + GQuark domain = G_IO_ERROR; + int code; - if (!no_xattr) + if (!SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) + return TRUE; + + switch (msg->status_code) { - if (!glnx_openat_rdonly (dfd, name, FALSE, - &fd, error)) - return FALSE; + case SOUP_STATUS_CANCELLED: + code = G_IO_ERROR_CANCELLED; + break; - if (save_cache_http_data_xattr (fd, bytes, error)) - return TRUE; + case SOUP_STATUS_CANT_RESOLVE: + case SOUP_STATUS_CANT_CONNECT: + code = G_IO_ERROR_HOST_NOT_FOUND; + break; - if (errno == ENOTSUP) - g_clear_error (error); - else - return FALSE; - } + case SOUP_STATUS_IO_ERROR: + code = G_IO_ERROR_CONNECTION_CLOSED; + break; - fallback_name = g_strconcat (name, CACHE_HTTP_SUFFIX, NULL); - if (!glnx_file_replace_contents_at (dfd, fallback_name, - g_bytes_get_data (bytes, NULL), - g_bytes_get_size (bytes), - 0, - cancellable, - error)) - return FALSE; + default: + code = G_IO_ERROR_FAILED; + } - return TRUE; + g_set_error (error, domain, code, + "Error connecting to server: %s", + soup_status_get_phrase (msg->status_code)); + return FALSE; } +/* The soup input stream was closed */ static void stream_closed (GObject *source, GAsyncResult *res, gpointer user_data) { @@ -355,6 +624,7 @@ stream_closed (GObject *source, GAsyncResult *res, gpointer user_data) g_main_context_wakeup (data->context); } +/* Got some data from the soup input stream */ static void load_uri_read_cb (GObject *source, GAsyncResult *res, gpointer user_data) { @@ -392,6 +662,7 @@ load_uri_read_cb (GObject *source, GAsyncResult *res, gpointer user_data) } else { + g_assert (data->content != NULL); data->downloaded_bytes += nread; g_string_append_len (data->content, data->buffer, nread); } @@ -408,6 +679,7 @@ load_uri_read_cb (GObject *source, GAsyncResult *res, gpointer user_data) load_uri_read_cb, data); } +/* The http header part of the request is ready */ static void load_uri_callback (GObject *source_object, GAsyncResult *res, @@ -420,81 +692,37 @@ load_uri_callback (GObject *source_object, in = soup_request_send_finish (SOUP_REQUEST (request), res, &data->error); if (in == NULL) { - /* data->error has been set */ g_main_context_wakeup (data->context); return; } g_autoptr(SoupMessage) msg = soup_request_http_get_message ((SoupRequestHTTP *) request); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) + + if (!check_soup_transfer_error (msg, &data->error)) { - int code; - GQuark domain = G_IO_ERROR; + g_main_context_wakeup (data->context); + return; + } - switch (msg->status_code) - { - case 304: - if (data->cache_data) - set_cache_http_data_from_headers (data->cache_data, msg); - - domain = FLATPAK_HTTP_ERROR; - code = FLATPAK_HTTP_ERROR_NOT_CHANGED; - break; - - case 401: - domain = FLATPAK_HTTP_ERROR; - code = FLATPAK_HTTP_ERROR_UNAUTHORIZED; - break; - - case 403: - case 404: - case 410: - code = G_IO_ERROR_NOT_FOUND; - break; - - case 408: - code = G_IO_ERROR_TIMED_OUT; - break; - - case SOUP_STATUS_CANCELLED: - code = G_IO_ERROR_CANCELLED; - break; - - case SOUP_STATUS_CANT_RESOLVE: - case SOUP_STATUS_CANT_CONNECT: - code = G_IO_ERROR_HOST_NOT_FOUND; - break; - - case SOUP_STATUS_INTERNAL_SERVER_ERROR: - /* The server did return something, but it was useless to us, so that’s basically equivalent to not returning */ - code = G_IO_ERROR_HOST_UNREACHABLE; - break; - - case SOUP_STATUS_IO_ERROR: -#if !GLIB_CHECK_VERSION(2, 44, 0) - code = G_IO_ERROR_BROKEN_PIPE; -#else - code = G_IO_ERROR_CONNECTION_CLOSED; -#endif - break; + /* We correctly made a connection, although it may be a http failure like 404. + The status and headers are valid on return, even of a http failure though. */ - default: - code = G_IO_ERROR_FAILED; - } + data->status = msg->status_code; + data->hdr_content_type = g_strdup (soup_message_headers_get_content_type (msg->response_headers, NULL)); + data->hdr_www_authenticate = g_strdup (soup_message_headers_get_one (msg->response_headers, "WWW-Authenticate")); + data->hdr_etag = g_strdup (soup_message_headers_get_one (msg->response_headers, "ETag")); + data->hdr_last_modified = g_strdup (soup_message_headers_get_one (msg->response_headers, "Last-Modified")); + data->hdr_cache_control = g_strdup (soup_message_headers_get_list (msg->response_headers, "Cache-Control")); + data->hdr_expires = g_strdup (soup_message_headers_get_list (msg->response_headers, "Expires")); - data->error = g_error_new (domain, code, - "Server returned status %u: %s", - msg->status_code, - soup_status_get_phrase (msg->status_code)); + if ((data->flags & FLATPAK_HTTP_FLAGS_NOCHECK_STATUS) == 0 && + !check_http_status (data->status, &data->error)) + { g_main_context_wakeup (data->context); return; } - if (data->cache_data) - set_cache_http_data_from_headers (data->cache_data, msg); - - if (data->content_type_out) - *data->content_type_out = g_strdup (soup_message_headers_get_content_type (msg->response_headers, NULL)); + /* All is good, write the body to the destination */ if (data->out_tmpfile) { @@ -528,7 +756,7 @@ load_uri_callback (GObject *source_object, load_uri_read_cb, data); } -SoupSession * +static SoupSession * flatpak_create_soup_session (const char *user_agent) { SoupSession *soup_session; @@ -537,8 +765,8 @@ flatpak_create_soup_session (const char *user_agent) soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, - SOUP_SESSION_TIMEOUT, 60, - SOUP_SESSION_IDLE_TIMEOUT, 60, + SOUP_SESSION_TIMEOUT, FLATPAK_HTTP_TIMEOUT_SECS, + SOUP_SESSION_IDLE_TIMEOUT, FLATPAK_HTTP_TIMEOUT_SECS, NULL); http_proxy = g_getenv ("http_proxy"); if (http_proxy) @@ -556,6 +784,102 @@ flatpak_create_soup_session (const char *user_agent) return soup_session; } +FlatpakHttpSession * +flatpak_create_http_session (const char *user_agent) +{ + return (FlatpakHttpSession *)flatpak_create_soup_session (user_agent); +} + +void +flatpak_http_session_free (FlatpakHttpSession* http_session) +{ + SoupSession *soup_session = (SoupSession *)http_session; + + g_object_unref (soup_session); +} + +static gboolean +flatpak_download_http_uri_once (FlatpakHttpSession *http_session, + LoadUriData *data, + const char *uri, + GError **error) +{ + SoupSession *soup_session = (SoupSession *)http_session; + g_autoptr(SoupRequestHTTP) request = NULL; + SoupMessage *m; + + g_info ("Loading %s using libsoup", uri); + + request = soup_session_request_http (soup_session, + (data->flags & FLATPAK_HTTP_FLAGS_HEAD) != 0 ? "HEAD" : "GET", + uri, error); + if (request == NULL) + return FALSE; + + m = soup_request_http_get_message (request); + + if (data->flags & FLATPAK_HTTP_FLAGS_ACCEPT_OCI) + soup_message_headers_replace (m->request_headers, "Accept", + FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFEST ", " FLATPAK_DOCKER_MEDIA_TYPE_IMAGE_MANIFEST2 ", " FLATPAK_OCI_MEDIA_TYPE_IMAGE_INDEX); + + if (data->auth) + { + g_autofree char *basic_auth = g_strdup_printf ("Basic %s", data->auth); + soup_message_headers_replace (m->request_headers, "Authorization", basic_auth); + } + + if (data->token) + { + g_autofree char *bearer_token = g_strdup_printf ("Bearer %s", data->token); + soup_message_headers_replace (m->request_headers, "Authorization", bearer_token); + } + + if (data->cache_data) + { + CacheHttpData *cache_data = data->cache_data; + + if (cache_data->etag && cache_data->etag[0]) + soup_message_headers_replace (m->request_headers, "If-None-Match", cache_data->etag); + else if (cache_data->last_modified != 0) + { + g_autoptr(GDateTime) date = g_date_time_new_from_unix_utc (cache_data->last_modified); + g_autofree char *date_str = flatpak_format_http_date (date); + soup_message_headers_replace (m->request_headers, "If-Modified-Since", date_str); + } + } + + if (data->flags & FLATPAK_HTTP_FLAGS_STORE_COMPRESSED) + { + soup_session_remove_feature_by_type (soup_session, SOUP_TYPE_CONTENT_DECODER); + soup_message_headers_replace (m->request_headers, "Accept-Encoding", "gzip"); + data->store_compressed = TRUE; + } + else if (!soup_session_has_feature (soup_session, SOUP_TYPE_CONTENT_DECODER)) + { + soup_session_add_feature_by_type (soup_session, SOUP_TYPE_CONTENT_DECODER); + data->store_compressed = FALSE; + } + + soup_request_send_async (SOUP_REQUEST (request), + data->cancellable, + load_uri_callback, data); + + while (data->error == NULL && !data->done) + g_main_context_iteration (data->context, TRUE); + + if (data->error) + { + g_propagate_error (error, g_steal_pointer (&data->error)); + return FALSE; + } + + g_info ("Received %" G_GUINT64_FORMAT " bytes", data->downloaded_bytes); + + return TRUE; +} + +#endif /* HAVE_SOUP */ + /* Check whether a particular operation should be retried. This is entirely * based on how it failed (if at all) last time, and whether the operation has * some retries left. The retry count is set when the operation is first @@ -576,91 +900,103 @@ flatpak_http_should_retry_request (const GError *error, g_error_matches (error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND) || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE) || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT) || -#if !GLIB_CHECK_VERSION(2, 44, 0) - g_error_matches (error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE) || -#else g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED) || -#endif g_error_matches (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND) || g_error_matches (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_TEMPORARY_FAILURE)) { - g_debug ("Should retry request (remaining: %u retries), due to transient error: %s", - n_retries_remaining, error->message); + g_info ("Should retry request (remaining: %u retries), due to transient error: %s", + n_retries_remaining, error->message); return TRUE; } return FALSE; } -static GBytes * -flatpak_load_http_uri_once (SoupSession *soup_session, - const char *uri, - FlatpakHTTPFlags flags, - const char *token, - FlatpakLoadUriProgress progress, - gpointer user_data, - char **out_content_type, - GCancellable *cancellable, - GError **error) +GBytes * +flatpak_load_uri_full (FlatpakHttpSession *http_session, + const char *uri, + FlatpakHTTPFlags flags, + const char *auth, + const char *token, + FlatpakLoadUriProgress progress, + gpointer user_data, + int *out_status, + char **out_content_type, + char **out_www_authenticate, + GCancellable *cancellable, + GError **error) { - GBytes *bytes = NULL; - g_autoptr(GMainContext) context = NULL; - g_autoptr(SoupRequestHTTP) request = NULL; - g_autoptr(GString) content = g_string_new (""); - LoadUriData data = { NULL }; - SoupMessage *m; + g_auto(LoadUriData) data = { NULL }; + g_autoptr(GError) local_error = NULL; + guint n_retries_remaining = DEFAULT_N_NETWORK_RETRIES; + g_autoptr(GMainContextPopDefault) main_context = NULL; + gboolean success = FALSE; - g_debug ("Loading %s using libsoup", uri); + /* Ensure we handle file: uris the same independent of backend */ + if (g_ascii_strncasecmp (uri, "file:", 5) == 0) + { + g_autoptr(GFile) file = g_file_new_for_uri (uri); + gchar *contents; + gsize len; - context = g_main_context_ref_thread_default (); + if (!g_file_load_contents (file, cancellable, &contents, &len, NULL, error)) + return NULL; + + return g_bytes_new_take (g_steal_pointer (&contents), len); + } + + main_context = flatpak_main_context_new_default (); - data.context = context; - data.content = content; + data.context = main_context; data.progress = progress; - data.cancellable = cancellable; data.user_data = user_data; data.last_progress_time = g_get_monotonic_time (); - data.content_type_out = out_content_type; + data.cancellable = cancellable; + data.flags = flags; + data.auth = auth; + data.token = token; - request = soup_session_request_http (soup_session, "GET", - uri, error); - if (request == NULL) - return NULL; + data.content = g_string_new (""); - m = soup_request_http_get_message (request); + do + { + if (n_retries_remaining < DEFAULT_N_NETWORK_RETRIES) + { + g_clear_error (&local_error); + reset_load_uri_data (&data); + } - if (flags & FLATPAK_HTTP_FLAGS_ACCEPT_OCI) - soup_message_headers_replace (m->request_headers, "Accept", - FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFEST ", " FLATPAK_DOCKER_MEDIA_TYPE_IMAGE_MANIFEST2 ", " FLATPAK_OCI_MEDIA_TYPE_IMAGE_INDEX); + success = flatpak_download_http_uri_once (http_session, &data, uri, &local_error); + if (success) + break; - if (token) - { - g_autofree char *bearer_token = g_strdup_printf ("Bearer %s", token); - soup_message_headers_replace (m->request_headers, "Authorization", bearer_token); + g_assert (local_error != NULL); } + while (flatpak_http_should_retry_request (local_error, n_retries_remaining--)); - soup_request_send_async (SOUP_REQUEST (request), - cancellable, - load_uri_callback, &data); + if (success) + { + if (out_content_type) + *out_content_type = g_steal_pointer (&data.hdr_content_type); - while (data.error == NULL && !data.done) - g_main_context_iteration (data.context, TRUE); + if (out_www_authenticate) + *out_www_authenticate = g_steal_pointer (&data.hdr_www_authenticate); - if (data.error) - { - g_propagate_error (error, data.error); - return NULL; - } + if (out_status) + *out_status = data.status; - bytes = g_string_free_to_bytes (g_steal_pointer (&content)); - g_debug ("Received %" G_GUINT64_FORMAT " bytes", data.downloaded_bytes); + return g_string_free_to_bytes (g_steal_pointer (&data.content)); + } - return bytes; + g_assert (local_error != NULL); + g_propagate_error (error, g_steal_pointer (&local_error)); + return NULL; } + GBytes * -flatpak_load_uri (SoupSession *soup_session, +flatpak_load_uri (FlatpakHttpSession *http_session, const char *uri, FlatpakHTTPFlags flags, const char *token, @@ -670,165 +1006,251 @@ flatpak_load_uri (SoupSession *soup_session, GCancellable *cancellable, GError **error) { + return flatpak_load_uri_full (http_session, uri, flags, NULL, token, + progress, user_data, NULL, out_content_type, NULL, + cancellable, error); +} + +gboolean +flatpak_download_http_uri (FlatpakHttpSession *http_session, + const char *uri, + FlatpakHTTPFlags flags, + GOutputStream *out, + const char *token, + FlatpakLoadUriProgress progress, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + g_auto(LoadUriData) data = { NULL }; g_autoptr(GError) local_error = NULL; guint n_retries_remaining = DEFAULT_N_NETWORK_RETRIES; g_autoptr(GMainContextPopDefault) main_context = NULL; + gboolean success = FALSE; main_context = flatpak_main_context_new_default (); - /* Ensure we handle file: uris always */ - if (g_ascii_strncasecmp (uri, "file:", 5) == 0) - { - g_autoptr(GFile) file = g_file_new_for_uri (uri); - gchar *contents; - gsize len; - - if (!g_file_load_contents (file, cancellable, &contents, &len, NULL, error)) - return NULL; + data.context = main_context; + data.progress = progress; + data.user_data = user_data; + data.last_progress_time = g_get_monotonic_time (); + data.cancellable = cancellable; + data.flags = flags; + data.token = token; - return g_bytes_new_take (g_steal_pointer (&contents), len); - } + data.out = out; do { - g_autoptr(GBytes) bytes = NULL; - if (n_retries_remaining < DEFAULT_N_NETWORK_RETRIES) { g_clear_error (&local_error); - - if (progress) - progress (0, user_data); /* Reset the progress */ + reset_load_uri_data (&data); } - bytes = flatpak_load_http_uri_once (soup_session, uri, flags, - token, progress, user_data, out_content_type, - cancellable, &local_error); + success = flatpak_download_http_uri_once (http_session, &data, uri, &local_error); + + if (success) + break; + + g_assert (local_error != NULL); - if (local_error == NULL) - return g_steal_pointer (&bytes); + /* If the output stream has already been written to we can't retry. + * TODO: use a range request to resume the download */ + if (data.downloaded_bytes > 0) + break; } while (flatpak_http_should_retry_request (local_error, n_retries_remaining--)); + if (success) + return TRUE; + g_assert (local_error != NULL); g_propagate_error (error, g_steal_pointer (&local_error)); - return NULL; + return FALSE; } -static gboolean -flatpak_download_http_uri_once (SoupSession *soup_session, - const char *uri, - FlatpakHTTPFlags flags, - GOutputStream *out, - const char *token, - FlatpakLoadUriProgress progress, - gpointer user_data, - guint64 *out_bytes_written, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(SoupRequestHTTP) request = NULL; - g_autoptr(GMainContext) context = NULL; - LoadUriData data = { NULL }; - SoupMessage *m; +/************************************************************************ + * Cached http support * + ***********************************************************************/ - g_debug ("Loading %s using libsoup", uri); +#define CACHE_HTTP_XATTR "user.flatpak.http" +#define CACHE_HTTP_SUFFIX ".flatpak.http" +#define CACHE_HTTP_TYPE "(sstt)" - context = g_main_context_ref_thread_default (); +static void +clear_cache_http_data (CacheHttpData *data, + gboolean clear_uri) +{ + if (clear_uri) + g_clear_pointer (&data->uri, g_free); + g_clear_pointer (&data->etag, g_free); + data->last_modified = 0; + data->expires = 0; +} - data.context = context; - data.out = out; - data.progress = progress; - data.cancellable = cancellable; - data.user_data = user_data; - data.last_progress_time = g_get_monotonic_time (); +static void +free_cache_http_data (CacheHttpData *data) +{ + clear_cache_http_data (data, TRUE); + g_free (data); +} - request = soup_session_request_http (soup_session, "GET", - uri, error); - if (request == NULL) - return FALSE; +G_DEFINE_AUTOPTR_CLEANUP_FUNC (CacheHttpData, free_cache_http_data) - m = soup_request_http_get_message (request); - if (flags & FLATPAK_HTTP_FLAGS_ACCEPT_OCI) - soup_message_headers_replace (m->request_headers, "Accept", - FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFEST ", " FLATPAK_DOCKER_MEDIA_TYPE_IMAGE_MANIFEST2); +static GBytes * +serialize_cache_http_data (CacheHttpData * data) +{ + g_autoptr(GVariant) cache_variant = NULL; - if (token) + cache_variant = g_variant_ref_sink (g_variant_new (CACHE_HTTP_TYPE, + data->uri, + data->etag ? data->etag : "", + data->last_modified, + data->expires)); + if (G_BYTE_ORDER != G_BIG_ENDIAN) { - g_autofree char *bearer_token = g_strdup_printf ("Bearer %s", token); - soup_message_headers_replace (m->request_headers, "Authorization", bearer_token); + g_autoptr(GVariant) tmp_variant = cache_variant; + cache_variant = g_variant_byteswap (tmp_variant); } - soup_request_send_async (SOUP_REQUEST (request), - cancellable, - load_uri_callback, &data); - - while (data.error == NULL && !data.done) - g_main_context_iteration (data.context, TRUE); + return g_variant_get_data_as_bytes (cache_variant); +} - if (out_bytes_written) - *out_bytes_written = data.downloaded_bytes; +static void +deserialize_cache_http_data (CacheHttpData *data, + GBytes *bytes) +{ + g_autoptr(GVariant) cache_variant = NULL; - if (data.error) + cache_variant = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE (CACHE_HTTP_TYPE), + bytes, + FALSE)); + if (G_BYTE_ORDER != G_BIG_ENDIAN) { - g_propagate_error (error, data.error); - return FALSE; + g_autoptr(GVariant) tmp_variant = cache_variant; + cache_variant = g_variant_byteswap (tmp_variant); } - g_debug ("Received %" G_GUINT64_FORMAT " bytes", data.downloaded_bytes); - - return TRUE; + g_variant_get (cache_variant, + CACHE_HTTP_TYPE, + &data->uri, + &data->etag, + &data->last_modified, + &data->expires); } -gboolean -flatpak_download_http_uri (SoupSession *soup_session, - const char *uri, - FlatpakHTTPFlags flags, - GOutputStream *out, - const char *token, - FlatpakLoadUriProgress progress, - gpointer user_data, - GCancellable *cancellable, - GError **error) +static CacheHttpData * +load_cache_http_data (int dfd, + char *name, + gboolean *no_xattr, + GCancellable *cancellable, + GError **error) { - g_autoptr(GError) local_error = NULL; - guint n_retries_remaining = DEFAULT_N_NETWORK_RETRIES; - g_autoptr(GMainContextPopDefault) main_context = NULL; + g_autoptr(CacheHttpData) data = NULL; + g_autoptr(GBytes) cache_bytes = glnx_lgetxattrat (dfd, name, + CACHE_HTTP_XATTR, + error); + if (cache_bytes == NULL) + { + if (errno == ENOTSUP) + { + g_autofree char *cache_file = NULL; + glnx_autofd int fd = -1; - main_context = flatpak_main_context_new_default (); + g_clear_error (error); + *no_xattr = TRUE; - do - { - guint64 bytes_written = 0; + cache_file = g_strconcat (name, CACHE_HTTP_SUFFIX, NULL); - if (n_retries_remaining < DEFAULT_N_NETWORK_RETRIES) - { - g_clear_error (&local_error); + if (!glnx_openat_rdonly (dfd, cache_file, FALSE, + &fd, error)) + return FALSE; - if (progress) - progress (0, user_data); /* Reset the progress */ + cache_bytes = glnx_fd_readall_bytes (fd, cancellable, error); + if (!cache_bytes) + return NULL; } - - if (flatpak_download_http_uri_once (soup_session, uri, flags, - out, token, - progress, user_data, - &bytes_written, - cancellable, &local_error)) + else if (errno == ENOENT || errno == ENODATA) { - g_assert (local_error == NULL); - return TRUE; + g_clear_error (error); + return g_new0 (CacheHttpData, 1); } + else + { + return NULL; + } + } - /* If the output stream has already been written to we can't retry. - * TODO: use a range request to resume the download */ - if (bytes_written > 0) - break; + + data = g_new0 (CacheHttpData, 1); + deserialize_cache_http_data (data, cache_bytes); + return g_steal_pointer (&data); +} + +static gboolean +save_cache_http_data_xattr (int fd, + GBytes *bytes, + GError **error) +{ + if (TEMP_FAILURE_RETRY (fsetxattr (fd, (char *) CACHE_HTTP_XATTR, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + 0)) < 0) + return glnx_throw_errno_prefix (error, "fsetxattr"); + + return TRUE; +} + +static gboolean +save_cache_http_data_fallback (int fd, + GBytes *bytes, + GError **error) +{ + if (glnx_loop_write (fd, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes)) < 0) + return glnx_throw_errno_prefix (error, "write"); + + return TRUE; +} + +static gboolean +save_cache_http_data_to_file (int dfd, + char *name, + GBytes *bytes, + gboolean no_xattr, + GCancellable *cancellable, + GError **error) +{ + glnx_autofd int fd = -1; + g_autofree char *fallback_name = NULL; + + if (!no_xattr) + { + if (!glnx_openat_rdonly (dfd, name, FALSE, + &fd, error)) + return FALSE; + + if (save_cache_http_data_xattr (fd, bytes, error)) + return TRUE; + + if (errno == ENOTSUP) + g_clear_error (error); + else + return FALSE; } - while (flatpak_http_should_retry_request (local_error, n_retries_remaining--)); - g_assert (local_error != NULL); - g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; + fallback_name = g_strconcat (name, CACHE_HTTP_SUFFIX, NULL); + if (!glnx_file_replace_contents_at (dfd, fallback_name, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + 0, + cancellable, + error)) + return FALSE; + + return TRUE; } static gboolean @@ -863,34 +1285,119 @@ sync_and_rename_tmpfile (GLnxTmpfile *tmpfile, return TRUE; } -static gboolean -flatpak_cache_http_uri_once (SoupSession *soup_session, - const char *uri, - FlatpakHTTPFlags flags, - int dest_dfd, - const char *dest_subpath, - FlatpakLoadUriProgress progress, - gpointer user_data, - GCancellable *cancellable, - GError **error) +static void +set_cache_http_data_from_headers (CacheHttpData *cache_data, + LoadUriData *data) { - g_autoptr(SoupRequestHTTP) request = NULL; - g_autoptr(GMainContext) context = NULL; + const char *etag = data->hdr_etag; + const char *last_modified = data->hdr_last_modified; + const char *cache_control = data->hdr_cache_control; + const char *expires = data->hdr_expires; + gboolean expires_computed = FALSE; + + /* The original HTTP 1/1 specification only required sending the ETag header in a 304 + * response, and implied that a cache might need to save the old Cache-Control + * values. The updated RFC 7232 from 2014 requires sending Cache-Control, ETags, and + * Expire if they would have been sent in the original 200 response, and recommends + * sending Last-Modified for requests without an etag. Since sending these headers was + * apparently normal previously, for simplicity we assume the RFC 7232 behavior and start + * from scratch for a 304 response. + */ + clear_cache_http_data (cache_data, FALSE); + + if (etag && *etag) + { + cache_data->etag = g_strdup (etag); + } + else if (last_modified && *last_modified) + { + g_autoptr(GDateTime) date = flatpak_parse_http_time (last_modified); + if (date) + cache_data->last_modified = g_date_time_to_unix (date); + } + + if (cache_control && *cache_control) + { + g_autoptr(GHashTable) params = flatpak_parse_http_header_param_list (cache_control); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, params); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + if (g_strcmp0 (key, "max-age") == 0) + { + char *end; + + char *max_age = value; + int max_age_sec = g_ascii_strtoll (max_age, &end, 10); + if (*max_age != '\0' && *end == '\0') + { + GTimeVal now; + g_get_current_time (&now); + cache_data->expires = now.tv_sec + max_age_sec; + expires_computed = TRUE; + } + } + else if (g_strcmp0 (key, "no-cache") == 0) + { + cache_data->expires = 0; + expires_computed = TRUE; + } + } + } + + if (!expires_computed && expires && *expires) + { + g_autoptr(GDateTime) date = flatpak_parse_http_time (expires); + if (date) + { + cache_data->expires = g_date_time_to_unix (date); + expires_computed = TRUE; + } + } + + if (!expires_computed) + { + /* If nothing implies an expires time, use 30 minutes. Browsers use + * 0.1 * (Date - Last-Modified), but it's clearly appropriate here, and + * better if server's send a value. + */ + GTimeVal now; + g_get_current_time (&now); + cache_data->expires = now.tv_sec + 1800; + } +} + +gboolean +flatpak_cache_http_uri (FlatpakHttpSession *http_session, + const char *uri, + FlatpakHTTPFlags flags, + int dest_dfd, + const char *dest_subpath, + FlatpakLoadUriProgress progress, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + g_auto(LoadUriData) data = { NULL }; + g_autoptr(GError) local_error = NULL; + guint n_retries_remaining = DEFAULT_N_NETWORK_RETRIES; g_autoptr(CacheHttpData) cache_data = NULL; + g_autoptr(GMainContextPopDefault) main_context = NULL; g_autofree char *parent_path = g_path_get_dirname (dest_subpath); g_autofree char *name = g_path_get_basename (dest_subpath); - glnx_autofd int dfd = -1; - gboolean no_xattr = FALSE; - LoadUriData data = { NULL }; g_auto(GLnxTmpfile) out_tmpfile = { 0 }; g_auto(GLnxTmpfile) cache_tmpfile = { 0 }; g_autoptr(GBytes) cache_bytes = NULL; - SoupMessage *m; + gboolean no_xattr = FALSE; + glnx_autofd int cache_dfd = -1; + gboolean success; - if (!glnx_opendirat (dest_dfd, parent_path, TRUE, &dfd, error)) + if (!glnx_opendirat (dest_dfd, parent_path, TRUE, &cache_dfd, error)) return FALSE; - cache_data = load_cache_http_data (dfd, name, &no_xattr, + cache_data = load_cache_http_data (cache_dfd, name, &no_xattr, cancellable, error); if (!cache_data) return FALSE; @@ -905,10 +1412,9 @@ flatpak_cache_http_uri_once (SoupSession *soup_session, g_get_current_time (&now); if (cache_data->expires > now.tv_sec) { - if (error) - *error = g_error_new (FLATPAK_HTTP_ERROR, - FLATPAK_HTTP_ERROR_NOT_CHANGED, - "Reusing cached value"); + g_set_error (error, FLATPAK_HTTP_ERROR, + FLATPAK_HTTP_ERROR_NOT_CHANGED, + "Reusing cached value"); return FALSE; } } @@ -916,83 +1422,66 @@ flatpak_cache_http_uri_once (SoupSession *soup_session, if (cache_data->uri == NULL) cache_data->uri = g_strdup (uri); - /* Must revalidate */ - - g_debug ("Loading %s using libsoup", uri); + /* Missing from cache, or expired so must revalidate via etag/last-modified headers */ - context = g_main_context_ref_thread_default (); + main_context = flatpak_main_context_new_default (); - data.context = context; - data.cache_data = cache_data; - data.out_tmpfile = &out_tmpfile; - data.out_tmpfile_parent_dfd = dfd; + data.context = main_context; data.progress = progress; - data.cancellable = cancellable; data.user_data = user_data; data.last_progress_time = g_get_monotonic_time (); + data.cancellable = cancellable; + data.flags = flags; - request = soup_session_request_http (soup_session, "GET", - uri, error); - if (request == NULL) - return FALSE; + data.cache_data = cache_data; - m = soup_request_http_get_message (request); + data.out_tmpfile = &out_tmpfile; + data.out_tmpfile_parent_dfd = cache_dfd; - if (cache_data->etag && cache_data->etag[0]) - soup_message_headers_replace (m->request_headers, "If-None-Match", cache_data->etag); - else if (cache_data->last_modified != 0) + do { - SoupDate *date = soup_date_new_from_time_t (cache_data->last_modified); - g_autofree char *date_str = soup_date_to_string (date, SOUP_DATE_HTTP); - soup_message_headers_replace (m->request_headers, "If-Modified-Since", date_str); - soup_date_free (date); - } + if (n_retries_remaining < DEFAULT_N_NETWORK_RETRIES) + { + g_clear_error (&local_error); + reset_load_uri_data (&data); + } - if (flags & FLATPAK_HTTP_FLAGS_ACCEPT_OCI) - soup_message_headers_replace (m->request_headers, "Accept", - FLATPAK_OCI_MEDIA_TYPE_IMAGE_MANIFEST ", " FLATPAK_DOCKER_MEDIA_TYPE_IMAGE_MANIFEST2); + success = flatpak_download_http_uri_once (http_session, &data, uri, &local_error); - if (flags & FLATPAK_HTTP_FLAGS_STORE_COMPRESSED) - { - soup_session_remove_feature_by_type (soup_session, SOUP_TYPE_CONTENT_DECODER); - soup_message_headers_replace (m->request_headers, "Accept-Encoding", - "gzip"); - data.store_compressed = TRUE; - } - else if (!soup_session_has_feature (soup_session, SOUP_TYPE_CONTENT_DECODER)) - soup_session_add_feature_by_type (soup_session, SOUP_TYPE_CONTENT_DECODER); + if (success) + break; - soup_request_send_async (SOUP_REQUEST (request), - cancellable, - load_uri_callback, &data); + g_assert (local_error != NULL); + } + while (flatpak_http_should_retry_request (local_error, n_retries_remaining--)); - while (data.error == NULL && !data.done) - g_main_context_iteration (data.context, TRUE); + /* Update the cache data on success or cache-valid */ + if (success || g_error_matches (local_error, FLATPAK_HTTP_ERROR, FLATPAK_HTTP_ERROR_NOT_CHANGED)) + { + set_cache_http_data_from_headers (cache_data, &data); + cache_bytes = serialize_cache_http_data (cache_data); + } - if (data.error) + if (local_error) { - if (data.error->domain == FLATPAK_HTTP_ERROR && - data.error->code == FLATPAK_HTTP_ERROR_NOT_CHANGED) + if (cache_bytes) { GError *tmp_error = NULL; - cache_bytes = serialize_cache_http_data (cache_data); - - if (!save_cache_http_data_to_file (dfd, name, cache_bytes, no_xattr, + if (!save_cache_http_data_to_file (cache_dfd, name, cache_bytes, no_xattr, cancellable, &tmp_error)) { - g_clear_error (&data.error); + g_clear_error (&local_error); g_propagate_error (error, tmp_error); return FALSE; } } - g_propagate_error (error, data.error); + g_propagate_error (error, g_steal_pointer (&local_error)); return FALSE; } - cache_bytes = serialize_cache_http_data (cache_data); if (!no_xattr) { if (!save_cache_http_data_xattr (out_tmpfile.fd, cache_bytes, error)) @@ -1007,7 +1496,7 @@ flatpak_cache_http_uri_once (SoupSession *soup_session, if (no_xattr) { - if (!glnx_open_tmpfile_linkable_at (dfd, ".", O_WRONLY, &cache_tmpfile, error)) + if (!glnx_open_tmpfile_linkable_at (cache_dfd, ".", O_WRONLY, &cache_tmpfile, error)) return FALSE; if (!save_cache_http_data_fallback (cache_tmpfile.fd, cache_bytes, error)) @@ -1025,50 +1514,5 @@ flatpak_cache_http_uri_once (SoupSession *soup_session, return FALSE; } - g_debug ("Received %" G_GUINT64_FORMAT " bytes", data.downloaded_bytes); - return TRUE; } - -gboolean -flatpak_cache_http_uri (SoupSession *soup_session, - const char *uri, - FlatpakHTTPFlags flags, - int dest_dfd, - const char *dest_subpath, - FlatpakLoadUriProgress progress, - gpointer user_data, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GError) local_error = NULL; - guint n_retries_remaining = DEFAULT_N_NETWORK_RETRIES; - g_autoptr(GMainContextPopDefault) main_context = NULL; - - main_context = flatpak_main_context_new_default (); - - do - { - if (n_retries_remaining < DEFAULT_N_NETWORK_RETRIES) - { - g_clear_error (&local_error); - - if (progress) - progress (0, user_data); /* Reset the progress */ - } - - if (flatpak_cache_http_uri_once (soup_session, uri, flags, - dest_dfd, dest_subpath, - progress, user_data, - cancellable, &local_error)) - { - g_assert (local_error == NULL); - return TRUE; - } - } - while (flatpak_http_should_retry_request (local_error, n_retries_remaining--)); - - g_assert (local_error != NULL); - g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; -} diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h index 7ddaa03b..ceecb5c6 100644 --- a/common/flatpak-utils-private.h +++ b/common/flatpak-utils-private.h @@ -102,9 +102,6 @@ gboolean flatpak_fail_error (GError **error, const char *fmt, ...) G_GNUC_PRINTF (3, 4); -void flatpak_debug2 (const char *format, - ...) G_GNUC_PRINTF (1, 2); - gint flatpak_strcmp0_ptr (gconstpointer a, gconstpointer b); @@ -132,6 +129,7 @@ gboolean flatpak_extension_matches_reason (const char *extension_id, gboolean default_value); const char * flatpak_get_bwrap (void); +gboolean flatpak_bwrap_is_unprivileged (void); char **flatpak_strv_sort_by_length (const char * const *strv); char **flatpak_strv_merge (char **strv1, @@ -359,6 +357,13 @@ g_hash_table_steal_extended (GHashTable *hash_table, } #endif +#if !GLIB_CHECK_VERSION (2, 62, 0) +void g_ptr_array_extend (GPtrArray *array_to_extend, + GPtrArray *array, + GCopyFunc func, + gpointer user_data); +#endif + #if !GLIB_CHECK_VERSION (2, 68, 0) guint g_string_replace (GString *string, const gchar *find, @@ -407,6 +412,10 @@ gboolean flatpak_switch_symlink_and_remove (const char *symlink_path, const char *target, GError **error); +char *flatpak_keyfile_get_string_non_empty (GKeyFile *keyfile, + const char *group, + const char *key); + GKeyFile * flatpak_parse_repofile (const char *remote_name, gboolean from_ref, GKeyFile *keyfile, @@ -738,14 +747,6 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakRepoTransaction, flatpak_repo_transaction_ #define AUTOLOCK(name) G_GNUC_UNUSED __attribute__((cleanup (flatpak_auto_unlock_helper))) GMutex * G_PASTE (auto_unlock, __LINE__) = flatpak_auto_lock_helper (&G_LOCK_NAME (name)) -#if !defined(SOUP_AUTOCLEANUPS_H) && !defined(__SOUP_AUTOCLEANUPS_H__) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupSession, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupMessage, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupRequest, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupRequestHTTP, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupURI, soup_uri_free) -#endif - #if !JSON_CHECK_VERSION (1, 1, 2) G_DEFINE_AUTOPTR_CLEANUP_FUNC (JsonArray, json_array_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC (JsonBuilder, g_object_unref) diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c index ba77ba74..e348dcbf 100644 --- a/common/flatpak-utils.c +++ b/common/flatpak-utils.c @@ -1,4 +1,4 @@ -/* +/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: * Copyright © 1995-1998 Free Software Foundation, Inc. * Copyright © 2014-2019 Red Hat, Inc * @@ -79,6 +79,8 @@ static const GDBusErrorEntry flatpak_error_entries[] = { {FLATPAK_ERROR_NOT_CACHED, "org.freedesktop.Flatpak.Error.NotCached"}, /* Since: 1.3.3 */ {FLATPAK_ERROR_REF_NOT_FOUND, "org.freedesktop.Flatpak.Error.RefNotFound"}, /* Since: 1.4.0 */ {FLATPAK_ERROR_PERMISSION_DENIED, "org.freedesktop.Flatpak.Error.PermissionDenied"}, /* Since: 1.5.1 */ + {FLATPAK_ERROR_AUTHENTICATION_FAILED, "org.freedesktop.Flatpak.Error.AuthenticationFailed"}, /* Since: 1.7.3 */ + {FLATPAK_ERROR_NOT_AUTHORIZED, "org.freedesktop.Flatpak.Error.NotAuthorized"}, /* Since: 1.7.3 */ }; typedef struct archive FlatpakAutoArchiveRead; @@ -118,18 +120,6 @@ flatpak_fail_error (GError **error, FlatpakError code, const char *fmt, ...) return FALSE; } -void -flatpak_debug2 (const char *format, ...) -{ - va_list var_args; - - va_start (var_args, format); - g_logv (G_LOG_DOMAIN "2", - G_LOG_LEVEL_DEBUG, - format, var_args); - va_end (var_args); -} - gboolean flatpak_write_update_checksum (GOutputStream *out, gconstpointer data, @@ -626,7 +616,7 @@ load_kernel_module_list (void) if (!g_file_get_contents ("/proc/modules", &modules_data, NULL, &error)) { - g_debug ("Failed to read /proc/modules: %s", error->message); + g_info ("Failed to read /proc/modules: %s", error->message); return modules; } @@ -750,6 +740,19 @@ flatpak_get_bwrap (void) } gboolean +flatpak_bwrap_is_unprivileged (void) +{ + const char *path = g_find_program_in_path (flatpak_get_bwrap ()); + struct stat st; + + /* Various features are supported only if bwrap exists and is not setuid */ + return + path != NULL && + stat (path, &st) == 0 && + (st.st_mode & S_ISUID) == 0; +} + +gboolean flatpak_get_allowed_exports (const char *source_path, const char *app_id, FlatpakContext *context, @@ -2262,6 +2265,20 @@ flatpak_summary_lookup_ref (GVariant *summary_v, return TRUE; } +char * +flatpak_keyfile_get_string_non_empty (GKeyFile *keyfile, + const char *group, + const char *key) +{ + g_autofree char *value = NULL; + + value = g_key_file_get_string (keyfile, group, key, NULL); + if (value != NULL && *value == '\0') + g_clear_pointer (&value, g_free); + + return g_steal_pointer (&value); +} + GKeyFile * flatpak_parse_repofile (const char *remote_name, gboolean from_ref, @@ -2368,15 +2385,23 @@ flatpak_parse_repofile (const char *remote_name, g_key_file_set_boolean (config, group, "gpg-verify", FALSE); } - collection_id = g_key_file_get_string (keyfile, source_group, - FLATPAK_REPO_DEPLOY_COLLECTION_ID_KEY, NULL); - if (collection_id != NULL && *collection_id == '\0') - g_clear_pointer (&collection_id, g_free); + /* We have a hierarchy of keys for setting the collection ID, which all have + * the same effect. The only difference is which versions of Flatpak support + * them, and therefore what P2P implementation is enabled by them: + * DeploySideloadCollectionID: supported by Flatpak >= 1.12.8 (1.7.1 + * introduced sideload support but this key was added late) + * DeployCollectionID: supported by Flatpak >= 1.0.6 (but fully supported in + * >= 1.2.0) + * CollectionID: supported by Flatpak >= 0.9.8 + */ + collection_id = flatpak_keyfile_get_string_non_empty (keyfile, source_group, + FLATPAK_REPO_DEPLOY_SIDELOAD_COLLECTION_ID_KEY); if (collection_id == NULL) - collection_id = g_key_file_get_string (keyfile, source_group, - FLATPAK_REPO_COLLECTION_ID_KEY, NULL); - if (collection_id != NULL && *collection_id == '\0') - g_clear_pointer (&collection_id, g_free); + collection_id = flatpak_keyfile_get_string_non_empty (keyfile, source_group, + FLATPAK_REPO_DEPLOY_COLLECTION_ID_KEY); + if (collection_id == NULL) + collection_id = flatpak_keyfile_get_string_non_empty (keyfile, source_group, + FLATPAK_REPO_COLLECTION_ID_KEY); if (collection_id != NULL) { if (gpg_key == NULL) @@ -3175,7 +3200,7 @@ flatpak_repo_save_digested_summary (OstreeRepo *repo, if (fstatat (repo_dfd, path, &stbuf, 0) == 0 && stbuf.st_size != 0) { - g_debug ("Reusing digested summary at %s for %s", path, name); + g_info ("Reusing digested summary at %s for %s", path, name); return g_steal_pointer (&digest); } @@ -3191,7 +3216,7 @@ flatpak_repo_save_digested_summary (OstreeRepo *repo, cancellable, error)) return NULL; - g_debug ("Wrote digested summary at %s for %s", path, name); + g_info ("Wrote digested summary at %s for %s", path, name); return g_steal_pointer (&digest); } @@ -3220,7 +3245,7 @@ flatpak_repo_save_digested_summary_delta (OstreeRepo *repo, if (fstatat (repo_dfd, path, &stbuf, 0) == 0 && stbuf.st_size == g_bytes_get_size (delta)) { - g_debug ("Reusing digested summary-diff for %s", filename); + g_info ("Reusing digested summary-diff for %s", filename); return TRUE; } @@ -3231,7 +3256,7 @@ flatpak_repo_save_digested_summary_delta (OstreeRepo *repo, cancellable, error)) return FALSE; - g_debug ("Wrote digested summary delta at %s", path); + g_info ("Wrote digested summary delta at %s", path); return TRUE; } @@ -3294,7 +3319,7 @@ populate_commit_data_cache (OstreeRepo *repo, if (cache_version < FLATPAK_XA_CACHE_VERSION) { /* Need to re-index to get all data */ - g_debug ("Old summary cache version %d, not using cache", cache_version); + g_info ("Old summary cache version %d, not using cache", cache_version); return NULL; } @@ -3316,7 +3341,7 @@ populate_commit_data_cache (OstreeRepo *repo, 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"); + g_info ("Invalid checksum for digested summary, not using cache"); return NULL; } digest = ostree_checksum_from_bytes (checksum_bytes); @@ -3330,7 +3355,7 @@ populate_commit_data_cache (OstreeRepo *repo, summary_v = flatpak_repo_load_digested_summary (repo, digest, NULL); if (summary_v == NULL) { - g_debug ("Failed to load digested summary %s, not using cache", digest); + g_info ("Failed to load digested summary %s, not using cache", digest); return NULL; } @@ -3362,7 +3387,7 @@ populate_commit_data_cache (OstreeRepo *repo, if (!var_metadata_lookup (commit_metadata, "xa.data", NULL, &xa_data_v) || !var_variant_is_type (xa_data_v, G_VARIANT_TYPE ("(tts)"))) { - g_debug ("Missing xa.data for ref %s, not using cache", ref); + g_info ("Missing xa.data for ref %s, not using cache", ref); return NULL; } @@ -3638,7 +3663,7 @@ _ostree_repo_static_delta_superblock_digest (OstreeRepo *repo, g_checksum_get_digest (checksum, digest, &len); return g_variant_new_from_data (G_VARIANT_TYPE ("ay"), - g_memdup (digest, len), len, + g_memdup2 (digest, len), len, FALSE, g_free, FALSE); } @@ -4267,7 +4292,7 @@ add_summary_metadata (OstreeRepo *repo, g_variant_builder_add (metadata_builder, "{sv}", "xa.deploy-collection-id", g_variant_new_string (collection_id)); else if (deploy_collection_id) - g_debug ("Ignoring deploy-collection-id=true because no collection ID is set."); + g_info ("Ignoring deploy-collection-id=true because no collection ID is set."); if (authenticator_name) g_variant_builder_add (metadata_builder, "{sv}", "xa.authenticator-name", @@ -4729,7 +4754,7 @@ flatpak_repo_gc_digested_summaries (OstreeRepo *repo, /* Keep all the referenced summaries */ if (g_hash_table_contains (digested_summary_cache, sha256)) { - g_debug ("Keeping referenced summary %s", dent->d_name); + g_info ("Keeping referenced summary %s", dent->d_name); continue; } /* Remove rest */ @@ -4745,7 +4770,7 @@ flatpak_repo_gc_digested_summaries (OstreeRepo *repo, /* Only keep deltas going to a generated summary */ if (g_hash_table_contains (digested_summaries, to_sha256)) { - g_debug ("Keeping delta to generated summary %s", dent->d_name); + g_info ("Keeping delta to generated summary %s", dent->d_name); continue; } /* Remove rest */ @@ -4769,7 +4794,7 @@ flatpak_repo_gc_digested_summaries (OstreeRepo *repo, if (remove) { - g_debug ("Removing old digested summary file %s", dent->d_name); + g_info ("Removing old digested summary file %s", dent->d_name); if (unlinkat (iter.fd, dent->d_name, 0) != 0) { glnx_set_error_from_errno (error); @@ -4777,7 +4802,7 @@ flatpak_repo_gc_digested_summaries (OstreeRepo *repo, } } else - g_debug ("Keeping unexpected summary file %s", dent->d_name); + g_info ("Keeping unexpected summary file %s", dent->d_name); } return TRUE; @@ -5292,7 +5317,7 @@ copy_icon (const char *id, if (!ostree_repo_file_ensure_resolved (OSTREE_REPO_FILE(icon_file), NULL)) { - g_debug ("No icon at size %s for %s", size, id); + g_info ("No icon at size %s for %s", size, id); return TRUE; } @@ -5595,9 +5620,9 @@ _flatpak_repo_generate_appstream (OstreeRepo *repo, const char *collection_id; if (subset != NULL && *subset != 0) - g_debug ("Generating appstream for %s, subset %s", arch, subset); + g_info ("Generating appstream for %s, subset %s", arch, subset); else - g_debug ("Generating appstream for %s", arch); + g_info ("Generating appstream for %s", arch); collection_id = ostree_repo_get_collection_id (repo); @@ -5657,7 +5682,7 @@ _flatpak_repo_generate_appstream (OstreeRepo *repo, if (var_metadata_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE, NULL, NULL) || var_metadata_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE, NULL, NULL)) { - g_debug (_("%s is end-of-life, ignoring for appstream"), flatpak_decomposed_get_ref (ref)); + g_info (_("%s is end-of-life, ignoring for appstream"), flatpak_decomposed_get_ref (ref)); continue; } @@ -5749,7 +5774,7 @@ _flatpak_repo_generate_appstream (OstreeRepo *repo, if (g_file_equal (root, parent_root)) { skip_commit = TRUE; - g_debug ("Not updating %s, no change", branch); + g_info ("Not updating %s, no change", branch); } } @@ -5800,7 +5825,7 @@ _flatpak_repo_generate_appstream (OstreeRepo *repo, } } - g_debug ("Creating appstream branch %s", branch); + g_info ("Creating appstream branch %s", branch); if (collection_id != NULL) { const OstreeCollectionRef collection_ref = { (char *) collection_id, branch }; @@ -6755,7 +6780,8 @@ flatpak_pull_from_bundle (OstreeRepo *repo, if (metadata == NULL) return FALSE; - metadata_size = strlen (metadata_contents); + if (metadata_contents != NULL) + metadata_size = strlen (metadata_contents); if (!ostree_repo_get_remote_option (repo, remote, "collection-id", NULL, &remote_collection_id, NULL)) @@ -6973,7 +6999,7 @@ flatpak_mirror_image_from_oci (FlatpakOciRegistry *dst_registry, if (delta_layer) { - g_debug ("Using OCI delta %s for layer %s", delta_layer->digest, layer->digest); + g_info ("Using OCI delta %s for layer %s", delta_layer->digest, layer->digest); g_autofree char *delta_digest = NULL; glnx_autofd int delta_fd = flatpak_oci_registry_download_blob (registry, oci_repository, FALSE, delta_layer->digest, (const char **)delta_layer->urls, @@ -7162,7 +7188,7 @@ flatpak_pull_from_oci (OstreeRepo *repo, if (delta_layer) { - g_debug ("Using OCI delta %s for layer %s", delta_layer->digest, layer->digest); + g_info ("Using OCI delta %s for layer %s", delta_layer->digest, layer->digest); expected_digest = image_config->rootfs.diff_ids[i]; /* The delta recreates the uncompressed tar so use that digest */ } else @@ -8224,7 +8250,7 @@ flatpak_log_dir_access (FlatpakDir *dir) if (dir_path != NULL) dir_path_str = g_file_get_path (dir_path); dir_name = flatpak_dir_get_name (dir); - g_debug ("Opening %s flatpak installation at path %s", dir_name, dir_path_str); + g_info ("Opening %s flatpak installation at path %s", dir_name, dir_path_str); } } @@ -9214,6 +9240,23 @@ running_under_sudo (void) return FALSE; } +#if !GLIB_CHECK_VERSION (2, 62, 0) +void +g_ptr_array_extend (GPtrArray *array_to_extend, + GPtrArray *array, + GCopyFunc func, + gpointer user_data) +{ + for (gsize i = 0; i < array->len; i++) + { + if (func) + g_ptr_array_add (array_to_extend, func (g_ptr_array_index (array, i), user_data)); + else + g_ptr_array_add (array_to_extend, g_ptr_array_index (array, i)); + } +} +#endif + #if !GLIB_CHECK_VERSION (2, 68, 0) /* All this code is backported directly from glib */ guint diff --git a/common/meson.build b/common/meson.build new file mode 100644 index 00000000..b87f22ac --- /dev/null +++ b/common/meson.build @@ -0,0 +1,263 @@ +# Copyright 2022 Collabora Ltd. +# SPDX-License-Identifier: LGPL-2.1-or-later + +public_headers = [ + 'flatpak-bundle-ref.h', + 'flatpak-error.h', + 'flatpak-installation.h', + 'flatpak-installed-ref.h', + 'flatpak-instance.h', + 'flatpak-portal-error.h', + 'flatpak-ref.h', + 'flatpak-related-ref.h', + 'flatpak-remote-ref.h', + 'flatpak-remote.h', + 'flatpak-transaction.h', + 'flatpak.h', +] + +install_headers( + public_headers, + subdir : 'flatpak', +) + +flatpak_version_macros = configure_file( + input : 'flatpak-version-macros.h.in', + output : 'flatpak-version-macros.h', + configuration : { + 'FLATPAK_MAJOR_VERSION' : flatpak_major_version, + 'FLATPAK_MINOR_VERSION' : flatpak_minor_version, + 'FLATPAK_MICRO_VERSION' : flatpak_micro_version, + }, + install_dir : get_option('includedir') / 'flatpak', +) + +# TODO: After the Autotools build system is removed, we can probably +# switch this to gnome.mkenums_simple, but it's easier to keep them +# consistent if we use the same templates +enums = gnome.mkenums( + 'flatpak-enum-types', + c_template : 'flatpak-enum-types.c.template', + h_template : 'flatpak-enum-types.h.template', + install_dir : get_option('includedir') / 'flatpak', + install_header : true, + sources : public_headers, +) + +flatpak_gdbus = gnome.gdbus_codegen( + 'flatpak-dbus-generated', + sources : [ + '../data/org.freedesktop.Flatpak.xml', + '../data/org.freedesktop.Flatpak.Authenticator.xml', + ], + interface_prefix : 'org.freedesktop.Flatpak.', + namespace : 'Flatpak', +) + +flatpak_document_gdbus = gnome.gdbus_codegen( + 'flatpak-document-dbus-generated', + sources: [ + '../data/org.freedesktop.portal.Documents.xml', + ], + interface_prefix : 'org.freedesktop.portal.', + namespace : 'XdpDbus', +) + +systemd_gdbus = gnome.gdbus_codegen( + 'flatpak-systemd-dbus-generated', + sources: [ + '../data/org.freedesktop.systemd1.xml', + ], + interface_prefix : 'org.freedesktop.systemd1.', + namespace : 'Systemd', +) + +variant_schema_compiler_command = [ + global_source_root / 'subprojects' / 'variant-schema-compiler' / 'variant-schema-compiler', +] + +if get_option('internal_checks') + variant_schema_compiler_command += ['--internal-validation'] +endif + +variant_schema_compiler_command += [ + '--outfile', '@OUTPUT0@', + '--outfile-header', '@OUTPUT1@', + '--prefix', 'var', + '@INPUT@', +] + +flatpak_variant = custom_target( + 'flatpak-variant-private.h', + input : [ + '../data/flatpak-variants.gv', + ], + output : [ + 'flatpak-variant-impl-private.h', + 'flatpak-variant-private.h', + ], + build_by_default : true, + command : variant_schema_compiler_command, +) + +built_headers = [ + enums[1], + flatpak_version_macros, + flatpak_gdbus[1], + flatpak_document_gdbus[1], + systemd_gdbus[1], + flatpak_variant[1], +] + +libflatpak_common_base = static_library( + 'flatpak-common-base', + dependencies : base_deps + [libglnx_dep], + gnu_symbol_visibility : 'hidden', + include_directories : [common_include_directories], + install : false, + sources : [ + 'flatpak-utils-base.c', + 'flatpak-utils-base-private.h', + ] + flatpak_gdbus + flatpak_document_gdbus, +) +libflatpak_common_base_dep = declare_dependency( + dependencies : base_deps + [libglnx_dep], + include_directories : [common_include_directories], + link_with : [ + libflatpak_common_base, + ], + sources : built_headers, +) + +sources = [ + 'flatpak-appdata.c', + 'flatpak-auth.c', + 'flatpak-bundle-ref.c', + 'flatpak-bwrap.c', + 'flatpak-chain-input-stream.c', + 'flatpak-context.c', + 'flatpak-dir.c', + 'flatpak-error.c', + 'flatpak-exports.c', + 'flatpak-installation.c', + 'flatpak-installed-ref.c', + 'flatpak-instance.c', + 'flatpak-json-oci.c', + 'flatpak-json.c', + 'flatpak-oci-registry.c', + 'flatpak-portal-error.c', + 'flatpak-progress.c', + 'flatpak-prune.c', + 'flatpak-ref-utils.c', + 'flatpak-ref.c', + 'flatpak-related-ref.c', + 'flatpak-remote-ref.c', + 'flatpak-remote.c', + 'flatpak-run.c', + 'flatpak-transaction.c', + 'flatpak-utils-http.c', + 'flatpak-utils.c', + 'flatpak-uri.c', + 'flatpak-zstd-decompressor.c', +] + +if malcontent_dep.found() + sources += ['flatpak-parental-controls.c'] +endif + +libflatpak_common = static_library( + 'flatpak-common', + dependencies : [ + base_deps, + dconf_dep, + gpgme_dep, + json_glib_dep, + libarchive_dep, + libcurl_dep, + libflatpak_common_base_dep, + libglnx_dep, + libostree_dep, + libseccomp_dep, + libsoup_dep, + libsystemd_dep, + libxml_dep, + libzstd_dep, + malcontent_dep, + polkit_agent_dep, + xau_dep, + ], + gnu_symbol_visibility : 'hidden', + include_directories : [common_include_directories], + install : false, + sources : enums + public_headers + sources + systemd_gdbus + [ + flatpak_variant[0], + flatpak_variant[1], + ], +) +libflatpak_common_dep = declare_dependency( + dependencies : [ + base_deps, + libflatpak_common_base_dep, + libglnx_dep, + ], + include_directories : [common_include_directories], + link_with : [ + libflatpak_common, + ], + sources : built_headers, +) + +libflatpak = library( + 'flatpak', + 'flatpak.c', + gnu_symbol_visibility : 'hidden', + include_directories : [common_include_directories], + install : true, + link_args : ['-export-dynamic'], + link_whole : [ + libflatpak_common_base, + libflatpak_common, + ], + soversion : '0', + version : '0.@0@.0'.format(flatpak_binary_age), +) +libflatpak_dep = declare_dependency( + dependencies : base_deps, + include_directories : [common_include_directories], + link_with : [ + libflatpak, + ], + sources : built_headers, +) + +test_libflatpak = executable( + 'test-libflatpak', + 'test-lib.c', + dependencies : base_deps + [libglnx_dep, libflatpak_dep], + install : false, +) + +if gir_dep.found() + gnome.generate_gir( + libflatpak, + export_packages : 'flatpak', + extra_args : [ + '-DFLATPAK_EXTERN=__attribute__((visibility("default"))) extern', + '-DFLATPAK_COMPILATION=1', + '--warn-all', + ], + header : 'flatpak.h', + identifier_prefix : 'Flatpak', + includes : ['GObject-2.0', 'Gio-2.0'], + install : true, + namespace : 'Flatpak', + nsversion : '1.0', + sources : [ + enums, + flatpak_version_macros, + public_headers, + sources, + ], + symbol_prefix : 'flatpak', + ) +endif |