| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| |
| #include "base/check_op.h" |
| #include "base/feature_list.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/strcat.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/proto/web_app_install_state.pb.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/browser/web_applications/web_app_registrar.h" |
| #include "components/crx_file/id_util.h" |
| #include "components/password_manager/content/common/web_ui_constants.h" |
| #include "components/webapps/common/web_app_id.h" |
| #include "content/public/common/url_constants.h" |
| #include "crypto/sha2.h" |
| #include "third_party/blink/public/mojom/manifest/manifest.mojom.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #include "url/url_constants.h" |
| |
| namespace web_app { |
| |
| namespace { |
| // The following string is used to concatenate the id of a sub-app with the id |
| // of the respective parent app, to produce a new id that is assured to not |
| // conflict with the id of the same app when installed as a standalone app. |
| const char kSubAppIdConcatenation[] = ":"; |
| |
| std::string MaybeConcatenateParentAppManifestId( |
| const webapps::ManifestId& manifest_id, |
| const std::optional<webapps::ManifestId>& parent_manifest_id) { |
| if (parent_manifest_id.has_value()) { |
| CHECK(parent_manifest_id->is_valid()); |
| CHECK_NE(parent_manifest_id.value(), manifest_id) |
| << "An app cannot be a parent to itself."; |
| return base::StrCat({manifest_id.GetWithoutRef().spec(), |
| kSubAppIdConcatenation, |
| parent_manifest_id->GetWithoutRef().spec()}); |
| } else { |
| return manifest_id.GetWithoutRef().spec(); |
| } |
| } |
| } // namespace |
| |
| // The following string is used to build the directory name for |
| // shortcuts to chrome applications (the kind which are installed |
| // from a CRX). Application shortcuts to URLs use the {host}_{path} |
| // for the name of this directory. Hosts can't include an underscore. |
| // By starting this string with an underscore, we ensure that there |
| // are no naming conflicts. |
| const char kCrxAppPrefix[] = "_crx_"; |
| |
| std::string GenerateApplicationNameFromURL(const GURL& url) { |
| return base::StrCat({url.host_piece(), "_", url.path_piece()}); |
| } |
| |
| std::string GenerateApplicationNameFromAppId(const webapps::AppId& app_id) { |
| std::string t(kCrxAppPrefix); |
| t.append(app_id); |
| return t; |
| } |
| |
| webapps::AppId GetAppIdFromApplicationName(const std::string& app_name) { |
| std::string prefix(kCrxAppPrefix); |
| if (app_name.substr(0, prefix.length()) != prefix) |
| return std::string(); |
| return app_name.substr(prefix.length()); |
| } |
| |
| webapps::AppId GenerateAppId( |
| const std::optional<std::string>& manifest_id_path, |
| const GURL& start_url, |
| const std::optional<webapps::ManifestId>& parent_manifest_id) { |
| if (!manifest_id_path) { |
| return GenerateAppIdFromManifestId( |
| GenerateManifestIdFromStartUrlOnly(start_url), parent_manifest_id); |
| } |
| return GenerateAppIdFromManifestId( |
| GenerateManifestId(manifest_id_path.value(), start_url), |
| parent_manifest_id); |
| } |
| |
| webapps::AppId GenerateAppIdFromManifest( |
| const blink::mojom::Manifest& manifest, |
| const std::optional<webapps::ManifestId>& parent_manifest_id) { |
| CHECK(manifest.id.is_valid()); |
| return GenerateAppIdFromManifestId(manifest.id, parent_manifest_id); |
| } |
| |
| webapps::AppId GenerateAppIdFromManifestId( |
| const webapps::ManifestId& manifest_id, |
| const std::optional<webapps::ManifestId>& parent_manifest_id) { |
| // The app ID is hashed twice: here and in GenerateId. |
| // The double-hashing is for historical reasons and it needs to stay |
| // this way for backwards compatibility. (Back then, a web app's input to the |
| // hash needed to be formatted like an extension public key.) |
| auto concatenated_manifest_id = |
| MaybeConcatenateParentAppManifestId(manifest_id, parent_manifest_id); |
| return crx_file::id_util::GenerateId( |
| crypto::SHA256HashString(concatenated_manifest_id)); |
| } |
| |
| webapps::ManifestId GenerateManifestIdFromStartUrlOnly(const GURL& start_url) { |
| CHECK(start_url.is_valid()) << start_url.spec(); |
| return start_url.GetWithoutRef(); |
| } |
| |
| webapps::ManifestId GenerateManifestId(const std::string& manifest_id_path, |
| const GURL& start_url) { |
| const webapps::ManifestId manifest_id = |
| GenerateManifestIdUnsafe(manifest_id_path, start_url); |
| CHECK(manifest_id.is_valid()) |
| << "start_url: " << start_url << ", manifest_id = " << manifest_id_path; |
| return manifest_id; |
| } |
| |
| webapps::ManifestId GenerateManifestIdUnsafe( |
| const std::string& manifest_id_path, |
| const GURL& start_url) { |
| // When manifest_id_path is specified, the manifest_id is generated from |
| // <start_url_origin>/<manifest_id_path>. |
| // Note: start_url.DeprecatedGetOriginAsURL().spec() returns the origin ending |
| // with slash. |
| const GURL manifest_id(start_url.DeprecatedGetOriginAsURL().spec() + |
| manifest_id_path); |
| return manifest_id.GetWithoutRef(); |
| } |
| |
| namespace { |
| |
| base::flat_set<std::string>& ValidChromeUrlHosts() { |
| static base::NoDestructor<base::flat_set<std::string>> hosts; |
| return *hosts.get(); |
| } |
| |
| } // namespace |
| |
| bool IsValidWebAppUrl(const GURL& app_url) { |
| if (app_url.is_empty() || app_url.inner_url()) |
| return false; |
| |
| bool allow_extension_apps = true; |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) |
| // Stop allowing apps to be extension urls when the shortcuts are separated - |
| // they can be extension urls instead. |
| allow_extension_apps = false; |
| #endif |
| |
| // TODO(crbug.com/40793595): Remove chrome-extension scheme. |
| return app_url.SchemeIs(url::kHttpScheme) || |
| app_url.SchemeIs(url::kHttpsScheme) || |
| (allow_extension_apps && app_url.SchemeIs("chrome-extension")) || |
| (app_url.SchemeIs(content::kChromeUIScheme) && |
| ((app_url.host() == password_manager::kChromeUIPasswordManagerHost) || |
| ValidChromeUrlHosts().contains(app_url.host()))); |
| } |
| |
| base::ScopedClosureRunner AddValidWebAppChromeUrlHostForTesting( // IN-TEST |
| const std::string& host) { |
| CHECK(ValidChromeUrlHosts().insert(host).second); |
| return base::ScopedClosureRunner(base::BindOnce( |
| [](const std::string& host) { |
| CHECK(ValidChromeUrlHosts().contains(host)); |
| ValidChromeUrlHosts().erase(host); |
| }, |
| host)); |
| } |
| |
| std::optional<webapps::AppId> FindInstalledAppWithUrlInScope(Profile* profile, |
| const GURL& url, |
| bool window_only) { |
| auto* provider = WebAppProvider::GetForLocalAppsUnchecked(profile); |
| return provider |
| ? provider->registrar_unsafe().FindBestAppWithUrlInScope( |
| url, window_only |
| ? web_app::WebAppFilter::OpensInDedicatedWindow() |
| : web_app::WebAppFilter::InstalledInChrome()) |
| : std::nullopt; |
| } |
| |
| bool IsNonLocallyInstalledAppWithUrlInScope(Profile* profile, const GURL& url) { |
| auto* provider = WebAppProvider::GetForWebApps(profile); |
| return provider ? provider->registrar_unsafe() |
| .FindBestAppWithUrlInScope( |
| url, web_app::WebAppFilter::IsSuggestedApp()) |
| .has_value() |
| : false; |
| } |
| |
| bool LooksLikePlaceholder(const WebApp& app) { |
| for (const auto& [install_source, config] : |
| app.management_to_external_config_map()) { |
| if (config.is_placeholder) { |
| return true; |
| } |
| for (const GURL& install_url : config.install_urls) { |
| if (app.untranslated_name() == install_url.spec()) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| } // namespace web_app |