blob: c068f1a145bce17259dc57369488ae4c523f4cd9 [file] [log] [blame]
// 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