blob: e97d2c8ac8b8e4feea418218a3bf4b27b00295ed [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/system_extensions/system_extensions_sandboxed_unpacker.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "content/public/common/url_constants.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "url/gurl.h"
namespace {
const constexpr char kManifestName[] = "manifest.json";
const constexpr char kIdKey[] = "id";
const constexpr char kTypeKey[] = "type";
const constexpr char kNameKey[] = "name";
const constexpr char kShortNameKey[] = "short_name";
const constexpr char kCompanionWebAppUrlKey[] = "companion_web_app_url";
const constexpr char kServiceWorkerUrlKey[] = "service_worker_url";
GURL GetBaseURL(const std::string& id, SystemExtensionType type) {
// The host is made of up a System Extension prefix based on the type and
// the System Extension Id.
base::StringPiece host_prefix;
switch (type) {
case SystemExtensionType::kEcho:
static constexpr char kSystemExtensionEchoPrefix[] =
"system-extension-echo-";
host_prefix = kSystemExtensionEchoPrefix;
break;
}
const std::string host = base::StrCat({host_prefix, id});
return GURL(base::StrCat({content::kChromeUIUntrustedScheme,
url::kStandardSchemeSeparator, host}));
}
} // namespace
SystemExtensionsSandboxedUnpacker::SystemExtensionsSandboxedUnpacker() =
default;
SystemExtensionsSandboxedUnpacker::~SystemExtensionsSandboxedUnpacker() =
default;
void SystemExtensionsSandboxedUnpacker::GetSystemExtensionFromDir(
base::FilePath system_extension_dir,
GetSystemExtensionFromCallback callback) {
io_helper_.AsyncCall(&IOHelper::ReadManifestInDirectory)
.WithArgs(system_extension_dir)
.Then(base::BindOnce(
&SystemExtensionsSandboxedUnpacker::OnSystemExtensionManifestRead,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void SystemExtensionsSandboxedUnpacker::OnSystemExtensionManifestRead(
GetSystemExtensionFromCallback callback,
SystemExtensionsStatusOr<std::string, Status> result) {
if (!result.ok()) {
std::move(callback).Run(result.status());
return;
}
GetSystemExtensionFromString(result.value(), std::move(callback));
}
void SystemExtensionsSandboxedUnpacker::GetSystemExtensionFromString(
base::StringPiece system_extension_manifest_string,
GetSystemExtensionFromCallback callback) {
data_decoder::DataDecoder::ParseJsonIsolated(
std::string(system_extension_manifest_string),
base::BindOnce(
&SystemExtensionsSandboxedUnpacker::OnSystemExtensionManifestParsed,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void SystemExtensionsSandboxedUnpacker::OnSystemExtensionManifestParsed(
GetSystemExtensionFromCallback callback,
data_decoder::DataDecoder::ValueOrError value_or_error) {
if (value_or_error.error.has_value()) {
std::move(callback).Run(Status::kFailedJsonErrorParsingManifest);
return;
}
base::Value& parsed_manifest = value_or_error.value.value();
SystemExtension system_extension;
// Parse mandatory fields.
// Parse id.
std::string* id_str = parsed_manifest.FindStringKey(kIdKey);
if (!id_str) {
std::move(callback).Run(Status::kFailedIdMissing);
return;
}
absl::optional<SystemExtensionId> id = SystemExtension::StringToId(*id_str);
if (!id.has_value()) {
std::move(callback).Run(Status::kFailedIdInvalid);
return;
}
system_extension.id = id.value();
// Parse type.
std::string* type_str = parsed_manifest.FindStringKey(kTypeKey);
if (!type_str) {
std::move(callback).Run(Status::kFailedTypeMissing);
return;
}
if (base::CompareCaseInsensitiveASCII("echo", *type_str) != 0) {
std::move(callback).Run(Status::kFailedTypeInvalid);
return;
}
system_extension.type = SystemExtensionType::kEcho;
// Parse base_url.
const GURL base_url = GetBaseURL(*id_str, system_extension.type);
// If both the type and id are valid, there is no possible way for the
// base_url to be invalid.
CHECK(base_url.is_valid());
system_extension.base_url = base_url;
// Parse service_worker_url.
std::string* service_worker_path =
parsed_manifest.FindStringKey(kServiceWorkerUrlKey);
if (!service_worker_path) {
std::move(callback).Run(Status::kFailedServiceWorkerUrlMissing);
return;
}
const GURL service_worker_url = base_url.Resolve(*service_worker_path);
if (!service_worker_url.is_valid() || service_worker_url == base_url) {
std::move(callback).Run(Status::kFailedServiceWorkerUrlInvalid);
return;
}
if (!url::IsSameOriginWith(base_url, service_worker_url)) {
std::move(callback).Run(Status::kFailedServiceWorkerUrlDifferentOrigin);
return;
}
system_extension.service_worker_url = service_worker_url;
// Parse name.
// TODO(ortuno): Decide a set of invalid characters and remove them/fail
// installation.
std::string* name = parsed_manifest.FindStringKey(kNameKey);
if (!name) {
std::move(callback).Run(Status::kFailedNameMissing);
return;
}
if (name->empty()) {
std::move(callback).Run(Status::kFailedNameEmpty);
return;
}
system_extension.name = *name;
// Parse optional fields.
// Parse short_name.
std::string* short_name_str = parsed_manifest.FindStringKey(kShortNameKey);
if (short_name_str && !short_name_str->empty()) {
system_extension.short_name = *short_name_str;
}
// Parse companion_web_app_url.
if (std::string* companion_web_app_url_str =
parsed_manifest.FindStringKey(kCompanionWebAppUrlKey)) {
GURL companion_web_app_url(*companion_web_app_url_str);
if (companion_web_app_url.is_valid() &&
companion_web_app_url.SchemeIs(url::kHttpsScheme)) {
system_extension.companion_web_app_url = companion_web_app_url;
} else {
LOG(WARNING) << "Companion Web App URL is invalid: "
<< companion_web_app_url;
}
}
std::move(callback).Run(std::move(system_extension));
}
SystemExtensionsSandboxedUnpacker::IOHelper::~IOHelper() = default;
SystemExtensionsStatusOr<std::string, SystemExtensionsSandboxedUnpacker::Status>
SystemExtensionsSandboxedUnpacker::IOHelper::ReadManifestInDirectory(
const base::FilePath& system_extension_dir) {
// Validate input |system_extension_dir|.
if (system_extension_dir.value().empty() ||
!base::DirectoryExists(system_extension_dir)) {
return Status::kFailedDirectoryMissing;
}
base::FilePath manifest_path = system_extension_dir.Append(kManifestName);
std::string manifest_contents;
if (!base::ReadFileToString(manifest_path, &manifest_contents)) {
return Status::kFailedManifestReadError;
}
return manifest_contents;
}