mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-01-30 02:54:47 +01:00
84f3ff2afd
As part of introducing the Chrome runtime we now need to distinguish between the classes that implement the current CEF runtime and the classes the implement the shared CEF library/runtime structure and public API. We choose the name Alloy for the current CEF runtime because it describes a combination of Chrome and other elements. Shared CEF library/runtime classes will continue to use the Cef prefix. Classes that implement the Alloy or Chrome runtime will use the Alloy or Chrome prefixes respectively. Classes that extend an existing Chrome-prefixed class will add the Cef or Alloy suffix, thereby following the existing naming pattern of Chrome-derived classes. This change applies the new naming pattern to an initial set of runtime-related classes. Additional classes/files will be renamed and moved as the Chrome runtime implementation progresses.
508 lines
18 KiB
C++
508 lines
18 KiB
C++
// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright
|
|
// 2013 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 "libcef/common/widevine_loader.h"
|
|
|
|
#if BUILDFLAG(ENABLE_WIDEVINE) && BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
|
|
|
#include "libcef/browser/context.h"
|
|
#include "libcef/browser/thread_util.h"
|
|
#include "libcef/common/cef_switches.h"
|
|
|
|
#include "base/command_line.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/json/json_string_value_serializer.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/native_library.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/strings/string_piece.h"
|
|
#include "base/strings/string_split.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "content/browser/plugin_service_impl.h"
|
|
#include "content/public/browser/cdm_registry.h"
|
|
#include "content/public/common/cdm_info.h"
|
|
#include "content/public/common/content_switches.h"
|
|
#include "media/cdm/cdm_host_file.h"
|
|
#include "media/cdm/supported_cdm_versions.h"
|
|
#include "services/service_manager/embedder/switches.h"
|
|
#include "services/service_manager/sandbox/switches.h"
|
|
#include "third_party/widevine/cdm/widevine_cdm_common.h" // nogncheck
|
|
|
|
namespace {
|
|
|
|
base::LazyInstance<CefWidevineLoader>::Leaky g_widevine_loader =
|
|
LAZY_INSTANCE_INITIALIZER;
|
|
|
|
// Based on chrome/browser/component_updater/widevine_cdm_component_installer.cc
|
|
|
|
// Name of the Widevine CDM OS in the component manifest.
|
|
const char kWidevineCdmOs[] =
|
|
#if defined(OS_MACOSX)
|
|
"mac";
|
|
#elif defined(OS_WIN)
|
|
"win";
|
|
#else // OS_LINUX, etc. TODO(viettrungluu): Separate out Chrome OS and Android?
|
|
"linux";
|
|
#endif
|
|
|
|
// Name of the Widevine CDM architecture in the component manifest.
|
|
const char kWidevineCdmArch[] =
|
|
#if defined(ARCH_CPU_X86)
|
|
"ia32"; // This differs from the component updater which uses "x86".
|
|
#elif defined(ARCH_CPU_X86_64)
|
|
"x64";
|
|
#else // TODO(viettrungluu): Support an ARM check?
|
|
"???";
|
|
#endif
|
|
|
|
// The CDM OS and architecture.
|
|
const char kCdmOsName[] = "os";
|
|
const char kCdmArchName[] = "arch";
|
|
|
|
// The CDM version (e.g. "1.4.8.903").
|
|
const char kCdmVersionName[] = "version";
|
|
|
|
// The CDM manifest includes several custom values, all beginning with "x-cdm-".
|
|
// All values are strings.
|
|
// All values that are lists are delimited by commas. No trailing commas.
|
|
// For example, "1,2,4".
|
|
const char kCdmValueDelimiter[] = ",";
|
|
// The following entries are required.
|
|
// Interface versions are lists of integers (e.g. "1" or "1,2,4").
|
|
// These are checked in this file before registering the CDM.
|
|
// All match the interface versions from content_decryption_module.h that the
|
|
// CDM supports.
|
|
// Matches CDM_MODULE_VERSION.
|
|
const char kCdmModuleVersionsName[] = "x-cdm-module-versions";
|
|
// Matches supported ContentDecryptionModule_* version(s).
|
|
const char kCdmInterfaceVersionsName[] = "x-cdm-interface-versions";
|
|
// Matches supported Host_* version(s).
|
|
const char kCdmHostVersionsName[] = "x-cdm-host-versions";
|
|
// The codecs list is a list of simple codec names (e.g. "vp8,vorbis").
|
|
// The list is passed to other parts of Chrome.
|
|
const char kCdmCodecsListName[] = "x-cdm-codecs";
|
|
// Whether persistent license is supported by the CDM: "true" or "false".
|
|
const char kCdmPersistentLicenseSupportName[] =
|
|
"x-cdm-persistent-license-support";
|
|
const char kCdmSupportedEncryptionSchemesName[] =
|
|
"x-cdm-supported-encryption-schemes";
|
|
|
|
// The following strings are used to specify supported codecs in the
|
|
// parameter |kCdmCodecsListName|.
|
|
const char kCdmSupportedCodecVp8[] = "vp8";
|
|
const char kCdmSupportedCodecVp9[] = "vp9.0";
|
|
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
|
|
const char kCdmSupportedCodecAvc1[] = "avc1";
|
|
#endif
|
|
|
|
// The following strings are used to specify supported encryption schemes in
|
|
// the parameter |kCdmSupportedEncryptionSchemesName|.
|
|
const char kCdmSupportedEncryptionSchemeCenc[] = "cenc";
|
|
const char kCdmSupportedEncryptionSchemeCbcs[] = "cbcs";
|
|
|
|
// Arguments passed to MakeCdmInfo.
|
|
struct CdmInfoArgs {
|
|
base::FilePath path;
|
|
std::string version;
|
|
content::CdmCapability capability;
|
|
};
|
|
|
|
std::unique_ptr<base::DictionaryValue> ParseManifestFile(
|
|
const base::FilePath& manifest_path) {
|
|
CEF_REQUIRE_BLOCKING();
|
|
|
|
// Manifest file should be < 1kb. Read at most 2kb.
|
|
std::string manifest_contents;
|
|
if (!base::ReadFileToStringWithMaxSize(manifest_path, &manifest_contents,
|
|
2048)) {
|
|
return nullptr;
|
|
}
|
|
|
|
JSONStringValueDeserializer deserializer(manifest_contents);
|
|
std::unique_ptr<base::Value> manifest(
|
|
deserializer.Deserialize(nullptr, nullptr));
|
|
|
|
if (!manifest.get() || !manifest->is_dict())
|
|
return nullptr;
|
|
|
|
// Transfer ownership to the caller.
|
|
return base::WrapUnique(
|
|
static_cast<base::DictionaryValue*>(manifest.release()));
|
|
}
|
|
|
|
std::string GetManifestValue(const base::DictionaryValue& manifest,
|
|
const std::string& key,
|
|
std::string* error_message) {
|
|
std::stringstream ss;
|
|
std::string value;
|
|
if (!manifest.GetString(key, &value)) {
|
|
ss << "Manifest missing " << key;
|
|
*error_message = ss.str();
|
|
} else if (value.empty()) {
|
|
ss << "Manifest has empty " << key;
|
|
*error_message = ss.str();
|
|
}
|
|
return value;
|
|
}
|
|
|
|
typedef bool (*VersionCheckFunc)(int version);
|
|
|
|
bool CheckForCompatibleVersion(const base::DictionaryValue& manifest,
|
|
const std::string version_name,
|
|
VersionCheckFunc version_check_func,
|
|
std::string* error_message) {
|
|
std::string versions_string =
|
|
GetManifestValue(manifest, version_name, error_message);
|
|
if (versions_string.empty())
|
|
return false;
|
|
|
|
for (const base::StringPiece& ver_str :
|
|
base::SplitStringPiece(versions_string, kCdmValueDelimiter,
|
|
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
|
|
int version = 0;
|
|
if (base::StringToInt(ver_str, &version))
|
|
if (version_check_func(version))
|
|
return true;
|
|
}
|
|
|
|
std::stringstream ss;
|
|
ss << "Manifest has no supported " << version_name << " in '"
|
|
<< versions_string << "'";
|
|
*error_message = ss.str();
|
|
return false;
|
|
}
|
|
|
|
// Returns whether the CDM's OS/platform and module/interface/host API versions,
|
|
// as specified in the manifest, are compatible with this Chromium binary.
|
|
bool IsCompatibleWithChrome(const base::DictionaryValue& manifest,
|
|
std::string* error_message) {
|
|
return GetManifestValue(manifest, kCdmOsName, error_message) ==
|
|
kWidevineCdmOs &&
|
|
GetManifestValue(manifest, kCdmArchName, error_message) ==
|
|
kWidevineCdmArch &&
|
|
CheckForCompatibleVersion(manifest, kCdmModuleVersionsName,
|
|
media::IsSupportedCdmModuleVersion,
|
|
error_message) &&
|
|
CheckForCompatibleVersion(manifest, kCdmInterfaceVersionsName,
|
|
media::IsSupportedCdmInterfaceVersion,
|
|
error_message) &&
|
|
CheckForCompatibleVersion(manifest, kCdmHostVersionsName,
|
|
media::IsSupportedCdmHostVersion,
|
|
error_message);
|
|
}
|
|
|
|
// Returns true and updates |video_codecs| if the appropriate manifest entry is
|
|
// valid. Returns false and does not modify |video_codecs| if the manifest entry
|
|
// is incorrectly formatted.
|
|
bool GetCodecs(const base::DictionaryValue& manifest,
|
|
std::vector<media::VideoCodec>* video_codecs,
|
|
std::string* error_message) {
|
|
DCHECK(video_codecs);
|
|
|
|
const base::Value* value = manifest.FindKey(kCdmCodecsListName);
|
|
if (!value) {
|
|
std::stringstream ss;
|
|
ss << "Widevine CDM component manifest is missing codecs.";
|
|
*error_message = ss.str();
|
|
return true;
|
|
}
|
|
|
|
if (!value->is_string()) {
|
|
std::stringstream ss;
|
|
ss << "Manifest entry " << kCdmCodecsListName << " is not a string.";
|
|
*error_message = ss.str();
|
|
return false;
|
|
}
|
|
|
|
const std::string& codecs = value->GetString();
|
|
if (codecs.empty()) {
|
|
std::stringstream ss;
|
|
ss << "Widevine CDM component manifest has empty codecs list.";
|
|
*error_message = ss.str();
|
|
return true;
|
|
}
|
|
|
|
std::vector<media::VideoCodec> result;
|
|
const std::vector<base::StringPiece> supported_codecs =
|
|
base::SplitStringPiece(codecs, kCdmValueDelimiter, base::TRIM_WHITESPACE,
|
|
base::SPLIT_WANT_NONEMPTY);
|
|
|
|
for (const auto& codec : supported_codecs) {
|
|
if (codec == kCdmSupportedCodecVp8)
|
|
result.push_back(media::VideoCodec::kCodecVP8);
|
|
else if (codec == kCdmSupportedCodecVp9)
|
|
result.push_back(media::VideoCodec::kCodecVP9);
|
|
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
|
|
else if (codec == kCdmSupportedCodecAvc1)
|
|
result.push_back(media::VideoCodec::kCodecH264);
|
|
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
|
|
}
|
|
|
|
video_codecs->swap(result);
|
|
return true;
|
|
}
|
|
|
|
// Returns true and updates |encryption_schemes| if the appropriate manifest
|
|
// entry is valid. Returns false and does not modify |encryption_schemes| if the
|
|
// manifest entry is incorrectly formatted. It is assumed that all CDMs support
|
|
// 'cenc', so if the manifest entry is missing, the result will indicate support
|
|
// for 'cenc' only. Incorrect types in the manifest entry will log the error and
|
|
// fail. Unrecognized values will be reported but otherwise ignored.
|
|
bool GetEncryptionSchemes(
|
|
const base::DictionaryValue& manifest,
|
|
base::flat_set<media::EncryptionScheme>* encryption_schemes,
|
|
std::string* error_message) {
|
|
DCHECK(encryption_schemes);
|
|
|
|
const base::Value* value =
|
|
manifest.FindKey(kCdmSupportedEncryptionSchemesName);
|
|
if (!value) {
|
|
// No manifest entry found, so assume only 'cenc' supported for backwards
|
|
// compatibility.
|
|
encryption_schemes->insert(media::EncryptionScheme::kCenc);
|
|
return true;
|
|
}
|
|
|
|
if (!value->is_list()) {
|
|
std::stringstream ss;
|
|
ss << "Manifest entry " << kCdmSupportedEncryptionSchemesName
|
|
<< " is not a list.";
|
|
*error_message = ss.str();
|
|
return false;
|
|
}
|
|
|
|
const base::span<const base::Value> list = value->GetList();
|
|
base::flat_set<media::EncryptionScheme> result;
|
|
for (const auto& item : list) {
|
|
if (!item.is_string()) {
|
|
std::stringstream ss;
|
|
ss << "Unrecognized item type in manifest entry "
|
|
<< kCdmSupportedEncryptionSchemesName;
|
|
*error_message = ss.str();
|
|
return false;
|
|
}
|
|
|
|
const std::string& scheme = item.GetString();
|
|
if (scheme == kCdmSupportedEncryptionSchemeCenc) {
|
|
result.insert(media::EncryptionScheme::kCenc);
|
|
} else if (scheme == kCdmSupportedEncryptionSchemeCbcs) {
|
|
result.insert(media::EncryptionScheme::kCbcs);
|
|
} else {
|
|
std::stringstream ss;
|
|
ss << "Unrecognized encryption scheme " << scheme << " in manifest entry "
|
|
<< kCdmSupportedEncryptionSchemesName;
|
|
*error_message = ss.str();
|
|
}
|
|
}
|
|
|
|
// As the manifest entry exists, it must specify at least one valid value.
|
|
if (result.empty())
|
|
return false;
|
|
|
|
encryption_schemes->swap(result);
|
|
return true;
|
|
}
|
|
|
|
// Returns true and updates |session_types| if the appropriate manifest entry is
|
|
// valid. Returns false if the manifest entry is incorrectly formatted.
|
|
bool GetSessionTypes(const base::DictionaryValue& manifest,
|
|
base::flat_set<media::CdmSessionType>* session_types,
|
|
std::string* error_message) {
|
|
DCHECK(session_types);
|
|
|
|
bool is_persistent_license_supported = false;
|
|
const base::Value* value = manifest.FindKey(kCdmPersistentLicenseSupportName);
|
|
if (value) {
|
|
if (!value->is_bool())
|
|
return false;
|
|
is_persistent_license_supported = value->GetBool();
|
|
}
|
|
|
|
// Temporary session is always supported.
|
|
session_types->insert(media::CdmSessionType::kTemporary);
|
|
if (is_persistent_license_supported)
|
|
session_types->insert(media::CdmSessionType::kPersistentLicense);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Verify and load the contents of |base_path|.
|
|
cef_cdm_registration_error_t LoadWidevineCdmInfo(
|
|
const base::FilePath& base_path,
|
|
CdmInfoArgs* args,
|
|
std::string* error_message) {
|
|
std::stringstream ss;
|
|
|
|
args->path = base_path.AppendASCII(
|
|
base::GetNativeLibraryName(kWidevineCdmLibraryName));
|
|
if (!base::PathExists(args->path)) {
|
|
ss << "Missing file " << args->path.value();
|
|
*error_message = ss.str();
|
|
return CEF_CDM_REGISTRATION_ERROR_INCORRECT_CONTENTS;
|
|
}
|
|
|
|
base::FilePath manifest_path = base_path.AppendASCII("manifest.json");
|
|
if (!base::PathExists(manifest_path)) {
|
|
ss << "Missing manifest file " << manifest_path.value();
|
|
*error_message = ss.str();
|
|
return CEF_CDM_REGISTRATION_ERROR_INCORRECT_CONTENTS;
|
|
}
|
|
|
|
std::unique_ptr<base::DictionaryValue> manifest =
|
|
ParseManifestFile(manifest_path);
|
|
if (!manifest) {
|
|
ss << "Failed to parse manifest file " << manifest_path.value();
|
|
*error_message = ss.str();
|
|
return CEF_CDM_REGISTRATION_ERROR_INCORRECT_CONTENTS;
|
|
}
|
|
|
|
if (!IsCompatibleWithChrome(*manifest, error_message))
|
|
return CEF_CDM_REGISTRATION_ERROR_INCOMPATIBLE;
|
|
|
|
args->version = GetManifestValue(*manifest, kCdmVersionName, error_message);
|
|
if (args->version.empty())
|
|
return CEF_CDM_REGISTRATION_ERROR_INCORRECT_CONTENTS;
|
|
|
|
if (!GetCodecs(*manifest, &args->capability.video_codecs, error_message) ||
|
|
!GetEncryptionSchemes(*manifest, &args->capability.encryption_schemes,
|
|
error_message) ||
|
|
!GetSessionTypes(*manifest, &args->capability.session_types,
|
|
error_message)) {
|
|
return CEF_CDM_REGISTRATION_ERROR_INCORRECT_CONTENTS;
|
|
}
|
|
|
|
return CEF_CDM_REGISTRATION_ERROR_NONE;
|
|
}
|
|
|
|
void DeliverWidevineCdmCallback(cef_cdm_registration_error_t result,
|
|
const std::string& error_message,
|
|
CefRefPtr<CefRegisterCdmCallback> callback) {
|
|
CEF_REQUIRE_UIT();
|
|
|
|
if (result != CEF_CDM_REGISTRATION_ERROR_NONE)
|
|
LOG(ERROR) << "Widevine CDM registration failed; " << error_message;
|
|
else if (!error_message.empty())
|
|
LOG(WARNING) << "Widevine CDM registration warning; " << error_message;
|
|
|
|
if (callback)
|
|
callback->OnCdmRegistrationComplete(result, error_message);
|
|
}
|
|
|
|
content::CdmInfo MakeCdmInfo(const CdmInfoArgs& args) {
|
|
return content::CdmInfo(kWidevineCdmDisplayName, kWidevineCdmGuid,
|
|
base::Version(args.version), args.path,
|
|
kWidevineCdmFileSystemId, args.capability,
|
|
kWidevineKeySystem, false);
|
|
}
|
|
|
|
void RegisterWidevineCdmOnUIThread(std::unique_ptr<CdmInfoArgs> args,
|
|
CefRefPtr<CefRegisterCdmCallback> callback) {
|
|
CEF_REQUIRE_UIT();
|
|
|
|
// Register Widevine with the CdmRegistry.
|
|
content::CdmRegistry::GetInstance()->RegisterCdm(MakeCdmInfo(*args));
|
|
|
|
DeliverWidevineCdmCallback(CEF_CDM_REGISTRATION_ERROR_NONE, std::string(),
|
|
callback);
|
|
}
|
|
|
|
void LoadWidevineCdmInfoOnBlockingThread(
|
|
const base::FilePath& base_path,
|
|
CefRefPtr<CefRegisterCdmCallback> callback) {
|
|
CEF_REQUIRE_BLOCKING();
|
|
|
|
std::unique_ptr<CdmInfoArgs> args = std::make_unique<CdmInfoArgs>();
|
|
std::string error_message;
|
|
cef_cdm_registration_error_t result =
|
|
LoadWidevineCdmInfo(base_path, args.get(), &error_message);
|
|
if (result != CEF_CDM_REGISTRATION_ERROR_NONE) {
|
|
CEF_POST_TASK(CEF_UIT, base::BindOnce(DeliverWidevineCdmCallback, result,
|
|
error_message, callback));
|
|
return;
|
|
}
|
|
|
|
// Continue execution on the UI thread.
|
|
CEF_POST_TASK(CEF_UIT, base::BindOnce(RegisterWidevineCdmOnUIThread,
|
|
std::move(args), callback));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// static
|
|
CefWidevineLoader* CefWidevineLoader::GetInstance() {
|
|
return &g_widevine_loader.Get();
|
|
}
|
|
|
|
void CefWidevineLoader::LoadWidevineCdm(
|
|
const base::FilePath& path,
|
|
CefRefPtr<CefRegisterCdmCallback> callback) {
|
|
if (!CONTEXT_STATE_VALID()) {
|
|
// Loading will proceed from OnContextInitialized().
|
|
load_pending_ = true;
|
|
path_ = path;
|
|
callback_ = callback;
|
|
return;
|
|
}
|
|
|
|
CEF_POST_USER_VISIBLE_TASK(
|
|
base::BindOnce(LoadWidevineCdmInfoOnBlockingThread, path, callback));
|
|
}
|
|
|
|
void CefWidevineLoader::OnContextInitialized() {
|
|
CEF_REQUIRE_UIT();
|
|
if (load_pending_) {
|
|
load_pending_ = false;
|
|
LoadWidevineCdm(path_, callback_);
|
|
callback_ = nullptr;
|
|
}
|
|
}
|
|
|
|
#if defined(OS_LINUX)
|
|
|
|
// static
|
|
void CefWidevineLoader::AddContentDecryptionModules(
|
|
std::vector<content::CdmInfo>* cdms,
|
|
std::vector<media::CdmHostFilePath>* cdm_host_file_paths) {
|
|
const base::CommandLine& command_line =
|
|
*base::CommandLine::ForCurrentProcess();
|
|
|
|
// Perform early plugin registration in the zygote process when the sandbox is
|
|
// enabled to avoid "cannot open shared object file: Operation not permitted"
|
|
// errors during plugin loading. This is because the Zygote process must pre-
|
|
// load all plugins before initializing the sandbox.
|
|
if (command_line.GetSwitchValueASCII(switches::kProcessType) !=
|
|
service_manager::switches::kZygoteProcess ||
|
|
command_line.HasSwitch(service_manager::switches::kNoSandbox)) {
|
|
return;
|
|
}
|
|
|
|
// The Widevine CDM path is passed to the zygote process via
|
|
// AlloyContentBrowserClient::AppendExtraCommandLineSwitches.
|
|
const base::FilePath& base_path =
|
|
command_line.GetSwitchValuePath(switches::kWidevineCdmPath);
|
|
if (base_path.empty())
|
|
return;
|
|
|
|
// Load contents of the plugin directory synchronously. This only occurs once
|
|
// on zygote process startup so should not have a huge performance penalty.
|
|
CdmInfoArgs args;
|
|
std::string error_message;
|
|
cef_cdm_registration_error_t result =
|
|
LoadWidevineCdmInfo(base_path, &args, &error_message);
|
|
if (result != CEF_CDM_REGISTRATION_ERROR_NONE) {
|
|
LOG(ERROR) << "Widevine CDM registration failed; " << error_message;
|
|
return;
|
|
}
|
|
|
|
cdms->push_back(MakeCdmInfo(args));
|
|
}
|
|
|
|
#endif // defined(OS_LINUX)
|
|
|
|
CefWidevineLoader::CefWidevineLoader() {}
|
|
|
|
CefWidevineLoader::~CefWidevineLoader() {}
|
|
|
|
#endif // BUILDFLAG(ENABLE_WIDEVINE) && BUILDFLAG(ENABLE_LIBRARY_CDMS)
|