2017-08-04 00:55:19 +02:00
|
|
|
// Copyright (c) 2017 The Chromium Embedded Framework 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 "tests/shared/browser/extension_util.h"
|
|
|
|
|
2021-06-20 18:08:10 +02:00
|
|
|
#include <algorithm>
|
2021-06-19 21:54:45 +02:00
|
|
|
#include <memory>
|
|
|
|
|
2021-06-17 22:08:01 +02:00
|
|
|
#include "include/base/cef_callback.h"
|
|
|
|
#include "include/base/cef_cxx17_backports.h"
|
2017-08-04 00:55:19 +02:00
|
|
|
#include "include/cef_parser.h"
|
|
|
|
#include "include/cef_path_util.h"
|
|
|
|
#include "include/wrapper/cef_closure_task.h"
|
|
|
|
#include "tests/shared/browser/file_util.h"
|
|
|
|
#include "tests/shared/browser/resource_util.h"
|
|
|
|
|
|
|
|
namespace client {
|
|
|
|
namespace extension_util {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
std::string GetResourcesPath() {
|
|
|
|
CefString resources_dir;
|
|
|
|
if (CefGetPath(PK_DIR_RESOURCES, resources_dir) && !resources_dir.empty()) {
|
|
|
|
return resources_dir.ToString() + file_util::kPathSep;
|
|
|
|
}
|
|
|
|
return std::string();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Internal extension paths may be prefixed with PK_DIR_RESOURCES and always
|
|
|
|
// use forward slash as path separator.
|
|
|
|
std::string GetInternalPath(const std::string& extension_path) {
|
2019-02-01 17:42:40 +01:00
|
|
|
std::string resources_path_lower = GetResourcesPath();
|
|
|
|
std::string extension_path_lower = extension_path;
|
|
|
|
|
|
|
|
#if defined(OS_WIN)
|
|
|
|
// Convert to lower-case, since Windows paths are case-insensitive.
|
|
|
|
std::transform(resources_path_lower.begin(), resources_path_lower.end(),
|
|
|
|
resources_path_lower.begin(), ::tolower);
|
|
|
|
std::transform(extension_path_lower.begin(), extension_path_lower.end(),
|
|
|
|
extension_path_lower.begin(), ::tolower);
|
|
|
|
#endif
|
|
|
|
|
2017-08-04 00:55:19 +02:00
|
|
|
std::string internal_path;
|
2019-02-01 17:42:40 +01:00
|
|
|
if (!resources_path_lower.empty() &&
|
2019-10-18 15:00:23 +02:00
|
|
|
extension_path_lower.find(resources_path_lower) == 0U) {
|
2019-02-01 17:42:40 +01:00
|
|
|
internal_path = extension_path.substr(resources_path_lower.size());
|
2017-08-04 00:55:19 +02:00
|
|
|
} else {
|
|
|
|
internal_path = extension_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(OS_WIN)
|
|
|
|
// Normalize path separators.
|
|
|
|
std::replace(internal_path.begin(), internal_path.end(), '\\', '/');
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return internal_path;
|
|
|
|
}
|
|
|
|
|
2021-06-19 21:54:45 +02:00
|
|
|
using ManifestCallback =
|
|
|
|
base::OnceCallback<void(CefRefPtr<CefDictionaryValue> /*manifest*/)>;
|
2017-08-04 00:55:19 +02:00
|
|
|
|
2021-06-19 21:54:45 +02:00
|
|
|
void RunManifestCallback(ManifestCallback callback,
|
2017-08-04 00:55:19 +02:00
|
|
|
CefRefPtr<CefDictionaryValue> manifest) {
|
|
|
|
if (!CefCurrentlyOn(TID_UI)) {
|
|
|
|
// Execute on the browser UI thread.
|
2021-06-19 21:54:45 +02:00
|
|
|
CefPostTask(TID_UI, base::BindOnce(std::move(callback), manifest));
|
2017-08-04 00:55:19 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-06-19 21:54:45 +02:00
|
|
|
std::move(callback).Run(manifest);
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Asynchronously reads the manifest and executes |callback| on the UI thread.
|
|
|
|
void GetInternalManifest(const std::string& extension_path,
|
2021-06-19 21:54:45 +02:00
|
|
|
ManifestCallback callback) {
|
2021-05-19 23:34:06 +02:00
|
|
|
if (!CefCurrentlyOn(TID_FILE_USER_BLOCKING)) {
|
2017-08-04 00:55:19 +02:00
|
|
|
// Execute on the browser FILE thread.
|
2021-05-19 23:34:06 +02:00
|
|
|
CefPostTask(TID_FILE_USER_BLOCKING,
|
2021-06-19 21:54:45 +02:00
|
|
|
base::BindOnce(GetInternalManifest, extension_path,
|
|
|
|
std::move(callback)));
|
2017-08-04 00:55:19 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& manifest_path = GetInternalExtensionResourcePath(
|
|
|
|
file_util::JoinPath(extension_path, "manifest.json"));
|
|
|
|
std::string manifest_contents;
|
|
|
|
if (!LoadBinaryResource(manifest_path.c_str(), manifest_contents) ||
|
|
|
|
manifest_contents.empty()) {
|
|
|
|
LOG(ERROR) << "Failed to load manifest from " << manifest_path;
|
2021-06-19 21:54:45 +02:00
|
|
|
RunManifestCallback(std::move(callback), nullptr);
|
2017-08-04 00:55:19 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CefString error_msg;
|
2020-10-08 21:54:42 +02:00
|
|
|
CefRefPtr<CefValue> value =
|
|
|
|
CefParseJSONAndReturnError(manifest_contents, JSON_PARSER_RFC, error_msg);
|
2017-08-04 00:55:19 +02:00
|
|
|
if (!value || value->GetType() != VTYPE_DICTIONARY) {
|
|
|
|
if (error_msg.empty())
|
|
|
|
error_msg = "Incorrectly formatted dictionary contents.";
|
|
|
|
LOG(ERROR) << "Failed to parse manifest from " << manifest_path << "; "
|
|
|
|
<< error_msg.ToString();
|
2021-06-19 21:54:45 +02:00
|
|
|
RunManifestCallback(std::move(callback), nullptr);
|
2017-08-04 00:55:19 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-19 21:54:45 +02:00
|
|
|
RunManifestCallback(std::move(callback), value->GetDictionary());
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void LoadExtensionWithManifest(CefRefPtr<CefRequestContext> request_context,
|
|
|
|
const std::string& extension_path,
|
|
|
|
CefRefPtr<CefExtensionHandler> handler,
|
|
|
|
CefRefPtr<CefDictionaryValue> manifest) {
|
|
|
|
CEF_REQUIRE_UI_THREAD();
|
|
|
|
|
|
|
|
// Load the extension internally. Resource requests will be handled via
|
|
|
|
// AddInternalExtensionToResourceManager.
|
|
|
|
request_context->LoadExtension(extension_path, manifest, handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
bool IsInternalExtension(const std::string& extension_path) {
|
|
|
|
// List of internally handled extensions.
|
|
|
|
static const char* extensions[] = {"set_page_color"};
|
|
|
|
|
|
|
|
const std::string& internal_path = GetInternalPath(extension_path);
|
2021-06-17 22:08:01 +02:00
|
|
|
for (size_t i = 0; i < base::size(extensions); ++i) {
|
2017-08-04 00:55:19 +02:00
|
|
|
// Exact match or first directory component.
|
|
|
|
const std::string& extension = extensions[i];
|
|
|
|
if (internal_path == extension ||
|
|
|
|
internal_path.find(extension + '/') == 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string GetInternalExtensionResourcePath(
|
|
|
|
const std::string& extension_path) {
|
|
|
|
return "extensions/" + GetInternalPath(extension_path);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string GetExtensionResourcePath(const std::string& extension_path,
|
|
|
|
bool* internal) {
|
|
|
|
const bool is_internal = IsInternalExtension(extension_path);
|
|
|
|
if (internal)
|
|
|
|
*internal = is_internal;
|
|
|
|
if (is_internal)
|
|
|
|
return GetInternalExtensionResourcePath(extension_path);
|
|
|
|
return extension_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool GetExtensionResourceContents(const std::string& extension_path,
|
|
|
|
std::string& contents) {
|
2021-05-19 23:34:06 +02:00
|
|
|
CEF_REQUIRE_FILE_USER_BLOCKING_THREAD();
|
2017-08-04 00:55:19 +02:00
|
|
|
|
|
|
|
if (IsInternalExtension(extension_path)) {
|
|
|
|
const std::string& contents_path =
|
|
|
|
GetInternalExtensionResourcePath(extension_path);
|
|
|
|
return LoadBinaryResource(contents_path.c_str(), contents);
|
|
|
|
}
|
|
|
|
|
|
|
|
return file_util::ReadFileToString(extension_path, &contents);
|
|
|
|
}
|
|
|
|
|
|
|
|
void LoadExtension(CefRefPtr<CefRequestContext> request_context,
|
|
|
|
const std::string& extension_path,
|
|
|
|
CefRefPtr<CefExtensionHandler> handler) {
|
|
|
|
if (!CefCurrentlyOn(TID_UI)) {
|
|
|
|
// Execute on the browser UI thread.
|
2021-06-19 21:54:45 +02:00
|
|
|
CefPostTask(TID_UI, base::BindOnce(LoadExtension, request_context,
|
|
|
|
extension_path, handler));
|
2017-08-04 00:55:19 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsInternalExtension(extension_path)) {
|
|
|
|
// Read the extension manifest and load asynchronously.
|
2021-06-19 21:54:45 +02:00
|
|
|
GetInternalManifest(
|
|
|
|
extension_path,
|
|
|
|
base::BindOnce(LoadExtensionWithManifest, request_context,
|
|
|
|
extension_path, handler));
|
2017-08-04 00:55:19 +02:00
|
|
|
} else {
|
|
|
|
// Load the extension from disk.
|
2020-01-15 15:28:12 +01:00
|
|
|
request_context->LoadExtension(extension_path, nullptr, handler);
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddInternalExtensionToResourceManager(
|
|
|
|
CefRefPtr<CefExtension> extension,
|
|
|
|
CefRefPtr<CefResourceManager> resource_manager) {
|
|
|
|
DCHECK(IsInternalExtension(extension->GetPath()));
|
|
|
|
|
|
|
|
if (!CefCurrentlyOn(TID_IO)) {
|
|
|
|
// Execute on the browser IO thread.
|
2021-06-19 21:54:45 +02:00
|
|
|
CefPostTask(TID_IO, base::BindOnce(AddInternalExtensionToResourceManager,
|
|
|
|
extension, resource_manager));
|
2017-08-04 00:55:19 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& origin = GetExtensionOrigin(extension->GetIdentifier());
|
|
|
|
const std::string& resource_path =
|
|
|
|
GetInternalExtensionResourcePath(extension->GetPath());
|
|
|
|
|
|
|
|
// Add provider for bundled resource files.
|
|
|
|
#if defined(OS_WIN)
|
|
|
|
// Read resources from the binary.
|
|
|
|
resource_manager->AddProvider(
|
|
|
|
CreateBinaryResourceProvider(origin, resource_path), 50, std::string());
|
|
|
|
#elif defined(OS_POSIX)
|
|
|
|
// Read resources from a directory on disk.
|
|
|
|
std::string resource_dir;
|
|
|
|
if (GetResourceDir(resource_dir)) {
|
|
|
|
resource_dir += "/" + resource_path;
|
|
|
|
resource_manager->AddDirectoryProvider(origin, resource_dir, 50,
|
|
|
|
std::string());
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string GetExtensionOrigin(const std::string& extension_id) {
|
|
|
|
return "chrome-extension://" + extension_id + "/";
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string GetExtensionURL(CefRefPtr<CefExtension> extension) {
|
|
|
|
CefRefPtr<CefDictionaryValue> browser_action =
|
|
|
|
extension->GetManifest()->GetDictionary("browser_action");
|
|
|
|
if (browser_action) {
|
|
|
|
const std::string& default_popup =
|
|
|
|
browser_action->GetString("default_popup");
|
|
|
|
if (!default_popup.empty())
|
|
|
|
return GetExtensionOrigin(extension->GetIdentifier()) + default_popup;
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::string();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string GetExtensionIconPath(CefRefPtr<CefExtension> extension,
|
|
|
|
bool* internal) {
|
|
|
|
CefRefPtr<CefDictionaryValue> browser_action =
|
|
|
|
extension->GetManifest()->GetDictionary("browser_action");
|
|
|
|
if (browser_action) {
|
|
|
|
const std::string& default_icon = browser_action->GetString("default_icon");
|
|
|
|
if (!default_icon.empty()) {
|
|
|
|
return GetExtensionResourcePath(
|
|
|
|
file_util::JoinPath(extension->GetPath(), default_icon), internal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::string();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace extension_util
|
|
|
|
} // namespace client
|