From bf07b8ceb2bf89c76f9b956ff1b90639adbd7ead Mon Sep 17 00:00:00 2001 From: Simon Hong Date: Thu, 7 Feb 2019 14:14:37 +0900 Subject: [PATCH] Support widevine in linux Widevine install is only downloaded when user accepts the use of widevine from google service via content bubble dialog. Then, it can be used after browser is restarted due to the use of zygote process in linux. Background update is only triggered when brave has different version of widevine with installed version. That means widevine_cdm_version.h included updated widevine version. This new version is also in effect after brwoser restart. We can update version string by changing widevine version in package.json. Of course, we could implement install and update w/o depending on version string. Instead it can be done by fetching latest version from google server. However, this could introduce many upstream file change because upstream code assumes version is not changed in runtime. BraveWidevineBundleManager manages widevine bundle's install state and it uses BraveWidevineBundleUnzipper to unzip downloaded zipped bundle. When installed, contents settings bubble text is changed to "restart". --- app/brave_generated_resources.grd | 17 + app/brave_main_delegate.cc | 17 +- browser/BUILD.gn | 4 + browser/brave_browser_main_extra_parts.cc | 14 + browser/brave_browser_process_impl.cc | 13 + browser/brave_browser_process_impl.h | 11 + browser/brave_profile_prefs.cc | 8 + browser/ui/BUILD.gn | 2 +- .../brave_content_setting_image_models.cc | 4 +- .../brave_widevine_blocked_image_model.cc | 19 +- ...idevine_blocked_image_model_browsertest.cc | 5 +- ...e_widevine_content_setting_bubble_model.cc | 62 +++- browser/widevine/BUILD.gn | 26 ++ .../widevine/brave_widevine_bundle_manager.cc | 346 ++++++++++++++++++ .../widevine/brave_widevine_bundle_manager.h | 108 ++++++ .../brave_widevine_bundle_manager_unittest.cc | 248 +++++++++++++ .../brave_widevine_bundle_unzipper.cc | 146 ++++++++ .../widevine/brave_widevine_bundle_unzipper.h | 75 ++++ common/brave_paths.cc | 24 ++ common/brave_paths.h | 5 +- common/pref_names.cc | 1 + common/pref_names.h | 1 + ...hird_party-widevine-cdm-widevine.gni.patch | 14 + test/BUILD.gn | 7 + 24 files changed, 1164 insertions(+), 13 deletions(-) create mode 100644 browser/widevine/BUILD.gn create mode 100644 browser/widevine/brave_widevine_bundle_manager.cc create mode 100644 browser/widevine/brave_widevine_bundle_manager.h create mode 100644 browser/widevine/brave_widevine_bundle_manager_unittest.cc create mode 100644 browser/widevine/brave_widevine_bundle_unzipper.cc create mode 100644 browser/widevine/brave_widevine_bundle_unzipper.h create mode 100644 patches/third_party-widevine-cdm-widevine.gni.patch diff --git a/app/brave_generated_resources.grd b/app/brave_generated_resources.grd index 61ac38800d9b..f4eb7c0ab3f2 100644 --- a/app/brave_generated_resources.grd +++ b/app/brave_generated_resources.grd @@ -169,6 +169,23 @@ Widevine is not installed + + + Install Widevine + + + Restart browser to enable Widevine + + + Widevine is not enabled + + + Widevine is not enabled + + + Widevine is not enabled + + Google Widevine is a piece of Digital Rights Management (DRM) code that we at Brave Software do not own and cannot inspect. The Google Widevine code is loaded from Google servers, not from our servers. It is loaded only when you enable this option. We discourage the use of DRM, but we respect user choice and acknowledge that some Brave users would like to use services that require it. diff --git a/app/brave_main_delegate.cc b/app/brave_main_delegate.cc index 4e3180afc6c2..5428fbd3a17b 100644 --- a/app/brave_main_delegate.cc +++ b/app/brave_main_delegate.cc @@ -29,8 +29,13 @@ #include "content/public/common/content_features.h" #include "extensions/common/extension_features.h" #include "gpu/config/gpu_finch_features.h" +#include "third_party/widevine/cdm/buildflags.h" #include "ui/base/ui_base_features.h" +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) +#include "brave/common/brave_paths.h" +#endif + #if BUILDFLAG(BRAVE_ADS_ENABLED) #include "components/dom_distiller/core/dom_distiller_switches.h" #endif @@ -144,5 +149,15 @@ bool BraveMainDelegate::BasicStartupComplete(int* exit_code) { enabled_features.str()); command_line.AppendSwitchASCII(switches::kDisableFeatures, disabled_features.str()); - return ChromeMainDelegate::BasicStartupComplete(exit_code); + + bool ret = ChromeMainDelegate::BasicStartupComplete(exit_code); + +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + // Override chrome::FILE_WIDEVINE_CDM path because we install it in user data + // dir. Must call after ChromeMainDelegate::BasicStartupComplete() to use + // chrome paths. + brave::OverridePath(); +#endif + + return ret; } diff --git a/browser/BUILD.gn b/browser/BUILD.gn index 87ba2a30c458..acb830824ea7 100644 --- a/browser/BUILD.gn +++ b/browser/BUILD.gn @@ -1,5 +1,6 @@ import("//brave/build/config.gni") import("//build/config/features.gni") +import("//third_party/widevine/cdm/widevine.gni") source_set("browser_process") { sources = [ @@ -79,8 +80,11 @@ source_set("browser_process") { "//content/public/browser", "//brave/chromium_src:browser", "themes", + "//third_party/widevine/cdm:buildflags", ] + if (bundle_widevine_cdm) { deps += [ "//brave/browser/widevine" ] } + if (is_win && is_official_build) { sources += [ "//chrome/browser/google/google_update_win.cc", diff --git a/browser/brave_browser_main_extra_parts.cc b/browser/brave_browser_main_extra_parts.cc index b5f4af5bb509..aed3b6ba3fa3 100644 --- a/browser/brave_browser_main_extra_parts.cc +++ b/browser/brave_browser_main_extra_parts.cc @@ -4,7 +4,13 @@ #include "brave/browser/brave_browser_main_extra_parts.h" +#include "brave/browser/brave_browser_process_impl.h" #include "chrome/browser/first_run/first_run.h" +#include "third_party/widevine/cdm/buildflags.h" + +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) +#include "brave/browser/widevine/brave_widevine_bundle_manager.h" +#endif BraveBrowserMainExtraParts::BraveBrowserMainExtraParts() { } @@ -14,4 +20,12 @@ BraveBrowserMainExtraParts::~BraveBrowserMainExtraParts() { void BraveBrowserMainExtraParts::PreMainMessageLoopRun() { brave::AutoImportMuon(); +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + // Want to check as early as possible because |StartupCheck()| has some + // fixup handling for abnormal status and run it on UI thread. + // However, BraveBrowserProcessImpl that the owner of bundle manager is + // created before browser thread creation. + // So, call it after browser threads are created. + g_brave_browser_process->brave_widevine_bundle_manager()->StartupCheck(); +#endif } diff --git a/browser/brave_browser_process_impl.cc b/browser/brave_browser_process_impl.cc index 3e6bcff851fd..6a2b130a0420 100644 --- a/browser/brave_browser_process_impl.cc +++ b/browser/brave_browser_process_impl.cc @@ -24,6 +24,10 @@ #include "components/component_updater/timer_update_scheduler.h" #include "content/public/browser/browser_thread.h" +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) +#include "brave/browser/widevine/brave_widevine_bundle_manager.h" +#endif + BraveBrowserProcessImpl* g_brave_browser_process = nullptr; using content::BrowserThread; @@ -149,3 +153,12 @@ void BraveBrowserProcessImpl::CreateProfileManager() { base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); profile_manager_ = std::make_unique(user_data_dir); } + +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) +BraveWidevineBundleManager* +BraveBrowserProcessImpl::brave_widevine_bundle_manager() { + if (!brave_widevine_bundle_manager_) + brave_widevine_bundle_manager_.reset(new BraveWidevineBundleManager); + return brave_widevine_bundle_manager_.get(); +} +#endif diff --git a/browser/brave_browser_process_impl.h b/browser/brave_browser_process_impl.h index 5386b49f1371..78611d3c01e0 100644 --- a/browser/brave_browser_process_impl.h +++ b/browser/brave_browser_process_impl.h @@ -6,12 +6,17 @@ #define BRAVE_BROWSER_BRAVE_BROWSER_PROCESS_IMPL_H_ #include "chrome/browser/browser_process_impl.h" +#include "third_party/widevine/cdm/buildflags.h" namespace brave { class BraveReferralsService; class BraveStatsUpdater; } +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) +class BraveWidevineBundleManager; +#endif + namespace brave_shields { class AdBlockService; class AdBlockRegionalService; @@ -40,6 +45,9 @@ class BraveBrowserProcessImpl : public BrowserProcessImpl { brave_shields::HTTPSEverywhereService* https_everywhere_service(); brave_shields::LocalDataFilesService* local_data_files_service(); extensions::BraveTorClientUpdater* tor_client_updater(); +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + BraveWidevineBundleManager* brave_widevine_bundle_manager(); +#endif private: void CreateProfileManager(); @@ -56,6 +64,9 @@ class BraveBrowserProcessImpl : public BrowserProcessImpl { std::unique_ptr brave_stats_updater_; std::unique_ptr brave_referrals_service_; std::unique_ptr tor_client_updater_; +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + std::unique_ptr brave_widevine_bundle_manager_; +#endif SEQUENCE_CHECKER(sequence_checker_); diff --git a/browser/brave_profile_prefs.cc b/browser/brave_profile_prefs.cc index 80907ecb5fbc..67df9b7f323a 100644 --- a/browser/brave_profile_prefs.cc +++ b/browser/brave_profile_prefs.cc @@ -19,6 +19,11 @@ #include "components/signin/core/browser/signin_pref_names.h" #include "components/spellcheck/browser/pref_names.h" #include "components/sync/base/pref_names.h" +#include "third_party/widevine/cdm/buildflags.h" + +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) +#include "brave/browser/widevine/brave_widevine_bundle_manager.h" +#endif namespace brave { @@ -35,6 +40,9 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { tor::TorProfileService::RegisterProfilePrefs(registry); registry->RegisterBooleanPref(kWidevineOptedIn, false); +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + BraveWidevineBundleManager::RegisterProfilePrefs(registry); +#endif // Default Brave shields registry->RegisterBooleanPref(kHTTPSEVerywhereControlType, true); diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index 82846ad875fb..910bb02be00c 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -89,7 +89,7 @@ source_set("ui") { "webui/settings/brave_default_extensions_handler.h", ] - if (enable_widevine_cdm_component) { + if (enable_widevine_cdm_component || bundle_widevine_cdm) { sources += [ "content_settings/brave_widevine_blocked_image_model.cc", "content_settings/brave_widevine_blocked_image_model.h", diff --git a/browser/ui/content_settings/brave_content_setting_image_models.cc b/browser/ui/content_settings/brave_content_setting_image_models.cc index 9bbd36c8095b..1d99933c07e8 100644 --- a/browser/ui/content_settings/brave_content_setting_image_models.cc +++ b/browser/ui/content_settings/brave_content_setting_image_models.cc @@ -7,7 +7,7 @@ #include "brave/browser/ui/content_settings/brave_autoplay_blocked_image_model.h" #include "third_party/widevine/cdm/buildflags.h" -#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) +#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) || BUILDFLAG(BUNDLE_WIDEVINE_CDM) #include "brave/browser/ui/content_settings/brave_widevine_blocked_image_model.h" #endif @@ -26,7 +26,7 @@ void BraveGenerateContentSettingImageModels( } } -#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) +#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) || BUILDFLAG(BUNDLE_WIDEVINE_CDM) result.push_back(std::make_unique( BraveWidevineBlockedImageModel::ImageType::PLUGINS, CONTENT_SETTINGS_TYPE_PLUGINS)); diff --git a/browser/ui/content_settings/brave_widevine_blocked_image_model.cc b/browser/ui/content_settings/brave_widevine_blocked_image_model.cc index 245ef45be619..88e53fa1803c 100644 --- a/browser/ui/content_settings/brave_widevine_blocked_image_model.cc +++ b/browser/ui/content_settings/brave_widevine_blocked_image_model.cc @@ -15,6 +15,13 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/paint_vector_icon.h" +#include "third_party/widevine/cdm/buildflags.h" + +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) +#include "brave/browser/brave_browser_process_impl.h" +#include "brave/browser/widevine/brave_widevine_bundle_manager.h" +#endif + using content::WebContents; BraveWidevineBlockedImageModel::BraveWidevineBlockedImageModel( @@ -35,8 +42,16 @@ bool BraveWidevineBlockedImageModel::UpdateAndGetVisibility(WebContents* web_con const gfx::VectorIcon* badge_id = &kBlockedBadgeIcon; const gfx::VectorIcon* icon = &kExtensionIcon; set_icon(*icon, *badge_id); - set_explanatory_string_id(IDS_WIDEVINE_NOT_INSTALLED_MESSAGE); - set_tooltip(l10n_util::GetStringUTF16(IDS_WIDEVINE_NOT_INSTALLED_EXPLANATORY_TEXT)); + + int message_id = IDS_WIDEVINE_NOT_INSTALLED_MESSAGE; + int tooltip_id = IDS_WIDEVINE_NOT_INSTALLED_EXPLANATORY_TEXT; +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + auto* manager = g_brave_browser_process->brave_widevine_bundle_manager(); + message_id = manager->GetWidevineBlockedImageMessage(); + tooltip_id = manager->GetWidevineBlockedImageTooltip(); +#endif + set_explanatory_string_id(message_id); + set_tooltip(l10n_util::GetStringUTF16(tooltip_id)); return true; } diff --git a/browser/ui/content_settings/brave_widevine_blocked_image_model_browsertest.cc b/browser/ui/content_settings/brave_widevine_blocked_image_model_browsertest.cc index e586b6d03a58..c9e7bc8e7c74 100644 --- a/browser/ui/content_settings/brave_widevine_blocked_image_model_browsertest.cc +++ b/browser/ui/content_settings/brave_widevine_blocked_image_model_browsertest.cc @@ -157,7 +157,7 @@ IN_PROC_BROWSER_TEST_F(BraveWidevineBlockedImageModelBrowserTest, ASSERT_FALSE(model->is_visible()); } -#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) +#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) || BUILDFLAG(BUNDLE_WIDEVINE_CDM) class BraveWidevineIconVisibilityBrowserTest : public CertVerifierBrowserTest { public: BraveWidevineIconVisibilityBrowserTest() @@ -248,4 +248,5 @@ IN_PROC_BROWSER_TEST_F(BraveWidevineIconVisibilityBrowserTest, EXPECT_TRUE(content::ExecuteScript(active_contents(), widevine_js)); EXPECT_TRUE(IsWidevineIconVisible()); } -#endif // BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) +#endif // BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) || + // BUILDFLAG(BUNDLE_WIDEVINE_CDM) diff --git a/browser/ui/content_settings/brave_widevine_content_setting_bubble_model.cc b/browser/ui/content_settings/brave_widevine_content_setting_bubble_model.cc index a0abd8c0018c..c6b227033cd0 100644 --- a/browser/ui/content_settings/brave_widevine_content_setting_bubble_model.cc +++ b/browser/ui/content_settings/brave_widevine_content_setting_bubble_model.cc @@ -8,17 +8,44 @@ #include "brave/browser/ui/brave_browser_content_setting_bubble_model_delegate.h" #include "brave/common/pref_names.h" #include "brave/components/brave_shields/common/brave_shield_constants.h" +#include "brave/grit/brave_generated_resources.h" #include "chrome/browser/component_updater/widevine_cdm_component_installer.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/plugins/plugin_utils.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/subresource_filter/chrome_subresource_filter_client.h" -#include "brave/grit/brave_generated_resources.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/prefs/pref_service.h" +#include "third_party/widevine/cdm/buildflags.h" #include "ui/base/l10n/l10n_util.h" -BraveWidevineContentSettingPluginBubbleModel::BraveWidevineContentSettingPluginBubbleModel( +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) +#include + +#include "base/bind.h" +#include "brave/browser/widevine/brave_widevine_bundle_manager.h" +#include "chrome/browser/lifetime/application_lifetime.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_window.h" +#endif + +namespace { +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) +void OnWidevineInstallDone(const std::string& error) { + if (!error.empty()) { + LOG(ERROR) << __func__ << ": " << error; + return; + } + + DVLOG(1) << __func__ << ": Widevine install success"; + if (Browser* browser = chrome::FindLastActive()) + browser->window()->UpdateToolbar(nullptr); +} +#endif +} + +BraveWidevineContentSettingPluginBubbleModel:: +BraveWidevineContentSettingPluginBubbleModel( ContentSettingBubbleModel::Delegate* delegate, content::WebContents* web_contents) : ContentSettingSimpleBubbleModel(delegate, @@ -49,15 +76,37 @@ void BraveWidevineContentSettingPluginBubbleModel::RunPluginsOnPage() { if (!web_contents()) return; +#if BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT) PrefService* prefs = ProfileManager::GetActiveUserProfile()->GetPrefs(); prefs->SetBoolean(kWidevineOptedIn, true); RegisterWidevineCdmComponent(g_brave_browser_process->component_updater()); ChromeSubresourceFilterClient::FromWebContents(web_contents()) ->OnReloadRequested(); +#endif + +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + auto* manager = g_brave_browser_process->brave_widevine_bundle_manager(); + if (manager->needs_restart()) { + manager->WillRestart(); + chrome::AttemptRelaunch(); + return; + } + // User can request install again because |kWidevineOptedIn| is set when + // install is finished. In this case, just waiting previous install request. + if (!manager->in_progress()) { + manager->InstallWidevineBundle(base::BindOnce(&OnWidevineInstallDone), + true); + } +#endif } void BraveWidevineContentSettingPluginBubbleModel::SetTitle() { - set_title(l10n_util::GetStringUTF16(IDS_NOT_INSTALLED_WIDEVINE_TITLE)); + int message_id = IDS_NOT_INSTALLED_WIDEVINE_TITLE; +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + auto* manager = g_brave_browser_process->brave_widevine_bundle_manager(); + message_id = manager->GetWidevineContentSettingsBubbleTitleText(); +#endif + set_title(l10n_util::GetStringUTF16(message_id)); } void BraveWidevineContentSettingPluginBubbleModel::SetMessage() { @@ -65,7 +114,12 @@ void BraveWidevineContentSettingPluginBubbleModel::SetMessage() { } void BraveWidevineContentSettingPluginBubbleModel::SetCustomLink() { - set_custom_link(l10n_util::GetStringUTF16(IDS_INSTALL_AND_RUN_WIDEVINE)); + int message_id = IDS_INSTALL_AND_RUN_WIDEVINE; +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + auto* manager = g_brave_browser_process->brave_widevine_bundle_manager(); + message_id = manager->GetWidevineContentSettingsBubbleLinkText(); +#endif + set_custom_link(l10n_util::GetStringUTF16(message_id)); set_custom_link_enabled(true); } diff --git a/browser/widevine/BUILD.gn b/browser/widevine/BUILD.gn new file mode 100644 index 000000000000..6fc4ce7688e0 --- /dev/null +++ b/browser/widevine/BUILD.gn @@ -0,0 +1,26 @@ +import("//third_party/widevine/cdm/widevine.gni") + +assert(bundle_widevine_cdm) + +source_set("widevine") { + sources = [ + "brave_widevine_bundle_manager.cc", + "brave_widevine_bundle_manager.h", + "brave_widevine_bundle_unzipper.cc", + "brave_widevine_bundle_unzipper.h", + ] + + deps = [ + "//base", + "//components/prefs", + "//components/pref_registry", + "//components/services/unzip/public/cpp", + "//content/public/browser", + "//content/public/common", + "//services/network/public/cpp", + "//services/service_manager/public/cpp", + "//third_party/widevine/cdm:buildflags", + "//third_party/widevine/cdm:headers", + "//url", + ] +} diff --git a/browser/widevine/brave_widevine_bundle_manager.cc b/browser/widevine/brave_widevine_bundle_manager.cc new file mode 100644 index 000000000000..c5b948d09876 --- /dev/null +++ b/browser/widevine/brave_widevine_bundle_manager.cc @@ -0,0 +1,346 @@ +/* Copyright (c) 2019 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/widevine/brave_widevine_bundle_manager.h" + +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/strings/string_piece.h" +#include "base/task/post_task.h" +#include "brave/browser/widevine/brave_widevine_bundle_unzipper.h" +#include "brave/common/pref_names.h" +#include "brave/grit/brave_generated_resources.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/net/system_network_context_manager.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/common/chrome_paths.h" +#include "components/prefs/pref_service.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/common/cdm_info.h" +#include "content/public/browser/cdm_registry.h" +#include "content/public/common/service_manager_connection.h" +#include "services/network/public/cpp/resource_request.h" +#include "services/service_manager/public/cpp/connector.h" +#include "third_party/widevine/cdm/widevine_cdm_common.h" +#include "url/gurl.h" +#include "widevine_cdm_version.h" + +namespace { + +constexpr int kWidevineBackgroundUpdateDelayInMins = 5; + +base::Optional GetTargetWidevineBundleDir() { + base::FilePath widevine_cdm_dir; + if (base::PathService::Get(chrome::DIR_USER_DATA, &widevine_cdm_dir)) { + widevine_cdm_dir = widevine_cdm_dir.Append( + FILE_PATH_LITERAL(kWidevineCdmBaseDirectory)); + base::CreateDirectory(widevine_cdm_dir); + return widevine_cdm_dir; + } + + return base::Optional(); +} + +void SetWidevinePrefs(bool enable) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + Profile* profile = ProfileManager::GetActiveUserProfile(); + DCHECK(profile); + + profile->GetPrefs()->SetBoolean(kWidevineOptedIn, enable); + profile->GetPrefs()->SetString( + kWidevineInstalledVersion, + enable ? WIDEVINE_CDM_VERSION_STRING + : BraveWidevineBundleManager::kWidevineInvalidVersion); +} + +} // namespace + +// static +char BraveWidevineBundleManager::kWidevineInvalidVersion[] = ""; + +// static +void BraveWidevineBundleManager::RegisterProfilePrefs( + user_prefs::PrefRegistrySyncable* registry) { + registry->RegisterStringPref(kWidevineInstalledVersion, + kWidevineInvalidVersion); +} + +BraveWidevineBundleManager::BraveWidevineBundleManager() : weak_factory_(this) { +} + +BraveWidevineBundleManager::~BraveWidevineBundleManager() { +} + +void BraveWidevineBundleManager::InstallWidevineBundle( + DoneCallback done_callback, + bool user_gesture) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DVLOG(1) << __func__ << (user_gesture ? ": Install widevine bundle" + : ": Update widevine bundle"); + DCHECK(!needs_restart()); + DCHECK(startup_checked_); + + done_callback_ = std::move(done_callback); + set_in_progress(true); + + DownloadWidevineBundle( + GURL(WIDEVINE_CDM_DOWNLOAD_URL_STRING), + base::BindOnce(&BraveWidevineBundleManager::OnBundleDownloaded, + base::Unretained(this))); +} + +void BraveWidevineBundleManager::DownloadWidevineBundle( + const GURL& bundle_zipfile_url, + network::SimpleURLLoader::DownloadToFileCompleteCallback callback) { + if (is_test_) return; + + net::NetworkTrafficAnnotationTag traffic_annotation = + net::DefineNetworkTrafficAnnotation("widevine_bundle_downloader", R"( + semantics { + sender: + "Brave Widevine Bundle Manager" + description: + "Download widevine cdm pkg" + trigger: + "When user accpets the use of widevine or update is started" + data: "Widevine cdm library package" + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: NO + setting: + "This feature can be disabled by disabling widevine in linux" + policy_exception_justification: + "Not implemented." + })"); + + auto request = std::make_unique(); + request->url = bundle_zipfile_url; + bundle_loader_ = + network::SimpleURLLoader::Create(std::move(request), traffic_annotation); + bundle_loader_->DownloadToTempFile( + g_browser_process->system_network_context_manager() + ->GetURLLoaderFactory(), + std::move(callback) + ); +} + +void BraveWidevineBundleManager::OnBundleDownloaded( + base::FilePath tmp_bundle_zip_file_path) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DVLOG(1) << __func__; + + if (tmp_bundle_zip_file_path.empty()) { + InstallDone("bundle file download failed"); + return; + } + + base::PostTaskAndReplyWithResult( + file_task_runner().get(), + FROM_HERE, + base::BindOnce(&GetTargetWidevineBundleDir), + base::BindOnce(&BraveWidevineBundleManager::OnGetTargetWidevineBundleDir, + weak_factory_.GetWeakPtr(), + tmp_bundle_zip_file_path)); +} + +void BraveWidevineBundleManager::OnGetTargetWidevineBundleDir( + const base::FilePath& tmp_bundle_zip_file_path, + base::Optional target_bundle_dir) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DVLOG(1) << __func__; + + if (!target_bundle_dir) { + InstallDone("getting target widevine dir failed"); + return; + } + + Unzip(tmp_bundle_zip_file_path, *target_bundle_dir); +} + +void BraveWidevineBundleManager::Unzip( + const base::FilePath& tmp_bundle_zip_file_path, + const base::FilePath& target_bundle_dir) { + if (is_test_) return; + + BraveWidevineBundleUnzipper::Create( + content::ServiceManagerConnection::GetForProcess()->GetConnector(), + file_task_runner(), + base::BindOnce(&BraveWidevineBundleManager::OnBundleUnzipped, + weak_factory_.GetWeakPtr())) + ->LoadFromZipFileInDir(tmp_bundle_zip_file_path, + target_bundle_dir, + true); +} + +void BraveWidevineBundleManager::OnBundleUnzipped(const std::string& error) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DVLOG(1) << __func__; + InstallDone(error); +} + +bool BraveWidevineBundleManager::in_progress() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + return in_progress_; +} + +void BraveWidevineBundleManager::set_in_progress(bool in_progress) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK_NE(in_progress_, in_progress); + DVLOG(1) << __func__ << ": " << in_progress; + + in_progress_ = in_progress; +} + +bool BraveWidevineBundleManager::needs_restart() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + return needs_restart_; +} + +void BraveWidevineBundleManager::set_needs_restart(bool needs_restart) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK_NE(needs_restart_, needs_restart); + DVLOG(1) << __func__ << ": " << needs_restart; + + needs_restart_ = needs_restart; +} + +void BraveWidevineBundleManager::InstallDone(const std::string& error) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + set_in_progress(false); + + // On success, marks that browser needs to restart to enable widevine. + // Otherwiase, reset prefs to initial state. + if (error.empty()) + set_needs_restart(true); + else + SetWidevinePrefs(false); + + std::move(done_callback_).Run(error); +} + +void BraveWidevineBundleManager::StartupCheck() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + startup_checked_ = true; + + const std::vector& cdms = + content::CdmRegistry::GetInstance()->GetAllRegisteredCdms(); + + auto has_widevine = [](const content::CdmInfo& cdm) { + return cdm.supported_key_system == kWidevineKeySystem; + }; + + // If cdms has widevine cdminfo, it means that filesystem has widevine lib. + if (std::find_if(cdms.begin(), cdms.end(), has_widevine) == cdms.end() ) { + DVLOG(1) << __func__ << ": reset widevine prefs state"; + // Widevine is not installed yet. Don't need to check. + // Also reset prefs to make as initial state. + SetWidevinePrefs(false); + return; + } + + Profile* profile = ProfileManager::GetActiveUserProfile(); + DCHECK(profile); + + // Although this case would be very rare, this might be happen because + // bundle unzipping and prefs setting is done asynchronously. + if (!profile->GetPrefs()->GetBoolean(kWidevineOptedIn)) { + DVLOG(1) << __func__ << ": recover invalid widevine prefs state"; + SetWidevinePrefs(true); + return; + } + + const std::string installed_version = + profile->GetPrefs()->GetString(kWidevineInstalledVersion); + + DVLOG(1) << __func__ << ": widevine prefs state looks fine"; + DVLOG(1) << __func__ << ": installed widevine version: " << installed_version; + + // Do delayed update if installed version and latest version are different. + if (installed_version != WIDEVINE_CDM_VERSION_STRING) { + update_requested_ = true; + base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&BraveWidevineBundleManager::DoDelayedBackgroundUpdate, + weak_factory_.GetWeakPtr()), + base::TimeDelta::FromMinutes(kWidevineBackgroundUpdateDelayInMins)); + } +} + +void BraveWidevineBundleManager::DoDelayedBackgroundUpdate() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + Profile* profile = ProfileManager::GetActiveUserProfile(); + const std::string installed_version = + profile->GetPrefs()->GetString(kWidevineInstalledVersion); + + DVLOG(1) << __func__ << ": update widevine" + << " from " << installed_version + << " to " << WIDEVINE_CDM_VERSION_STRING; + + InstallWidevineBundle(base::BindOnce([](const std::string& error) { + if (!error.empty()) { + LOG(ERROR) << __func__ << ": " << error; + return; + } + + DVLOG(1) << __func__ << ": Widevine update success"; + }), + false); +} + +int +BraveWidevineBundleManager::GetWidevineContentSettingsBubbleTitleText() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + return needs_restart() ? IDS_NOT_ENABLED_WIDEVINE_TITLE + : IDS_NOT_INSTALLED_WIDEVINE_TITLE; +} + +int +BraveWidevineBundleManager::GetWidevineContentSettingsBubbleLinkText() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + return needs_restart() ? IDS_WIDEVINE_RESTART_BROWSER : IDS_WIDEVINE_INSTALL; +} + +int BraveWidevineBundleManager::GetWidevineBlockedImageMessage() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + return needs_restart() ? IDS_WIDEVINE_NOT_ENABLED_MESSAGE + : IDS_WIDEVINE_NOT_INSTALLED_MESSAGE; +} + + +int BraveWidevineBundleManager::GetWidevineBlockedImageTooltip() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + return needs_restart() ? IDS_WIDEVINE_NOT_ENABLED_EXPLANATORY_TEXT + : IDS_WIDEVINE_NOT_INSTALLED_EXPLANATORY_TEXT; +} + +void BraveWidevineBundleManager::WillRestart() const { + DCHECK(needs_restart()); + SetWidevinePrefs(true); + DVLOG(1) << __func__; +} + +scoped_refptr +BraveWidevineBundleManager::file_task_runner() { + if (!file_task_runner_) { + file_task_runner_ = base::CreateSequencedTaskRunnerWithTraits( + { base::MayBlock(), + base::TaskPriority::BEST_EFFORT, + base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN }); + } + return file_task_runner_; +} diff --git a/browser/widevine/brave_widevine_bundle_manager.h b/browser/widevine/brave_widevine_bundle_manager.h new file mode 100644 index 000000000000..072f7ec34b6f --- /dev/null +++ b/browser/widevine/brave_widevine_bundle_manager.h @@ -0,0 +1,108 @@ +/* Copyright (c) 2019 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_WIDEVINE_BRAVE_WIDEVINE_BUNDLE_MANAGER_H_ +#define BRAVE_BROWSER_WIDEVINE_BRAVE_WIDEVINE_BUNDLE_MANAGER_H_ + +#include +#include + +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/strings/string_piece_forward.h" +#include "services/network/public/cpp/simple_url_loader.h" + +class GURL; + +namespace base { +class FilePath; +class SequencedTaskRunner; +} + +namespace user_prefs { +class PrefRegistrySyncable; +} + +class BraveWidevineBundleManager { + public: + static char kWidevineInvalidVersion[]; + static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); + + // Called when install process is finished. + using DoneCallback = base::OnceCallback; + + BraveWidevineBundleManager(); + ~BraveWidevineBundleManager(); + + void InstallWidevineBundle(DoneCallback done_callback, bool user_gesture); + + // Check consistency of library file, installed version and latest version + // and fix mismatches if needed. Also backgroud update is triggered if + // installed version is outdated. + void StartupCheck(); + + // Returns true when install/update is in progress. + // True doesn't mean install/update success. It means just finishing of + // install/update request. + bool in_progress() const; + + // Returns true when newly installed/updated version is ready. + bool needs_restart() const; + + int GetWidevineContentSettingsBubbleTitleText() const; + int GetWidevineContentSettingsBubbleLinkText() const; + int GetWidevineBlockedImageMessage() const; + int GetWidevineBlockedImageTooltip() const; + + void WillRestart() const; + + private: + friend class BraveWidevineBundleManagerTest; + FRIEND_TEST_ALL_PREFIXES(BraveWidevineBundleManagerTest, InProgressTest); + FRIEND_TEST_ALL_PREFIXES(BraveWidevineBundleManagerTest, UpdateTriggerTest); + FRIEND_TEST_ALL_PREFIXES(BraveWidevineBundleManagerTest, InstallSuccessTest); + FRIEND_TEST_ALL_PREFIXES(BraveWidevineBundleManagerTest, DownloadFailTest); + FRIEND_TEST_ALL_PREFIXES(BraveWidevineBundleManagerTest, UnzipFailTest); + FRIEND_TEST_ALL_PREFIXES(BraveWidevineBundleManagerTest, MessageStringTest); + FRIEND_TEST_ALL_PREFIXES(BraveWidevineBundleManagerTest, + RetryInstallAfterFail); + + void DownloadWidevineBundle( + const GURL& bundle_zipfile_url, + network::SimpleURLLoader::DownloadToFileCompleteCallback callback); + void OnBundleDownloaded(base::FilePath tmp_bundle_zip_file_path); + void OnGetTargetWidevineBundleDir( + const base::FilePath& tmp_bundle_zip_file_path, + base::Optional target_bundle_dir); + void set_in_progress(bool in_progress); + void set_needs_restart(bool needs_restart); + void Unzip(const base::FilePath& tmp_bundle_zip_file_path, + const base::FilePath& target_bundle_dir); + void OnBundleUnzipped(const std::string& error); + + void InstallDone(const std::string& error); + void DoDelayedBackgroundUpdate(); + + scoped_refptr file_task_runner(); + + bool is_test_ = false; + bool update_requested_ = false; + bool startup_checked_ = false; + DoneCallback done_callback_; + bool in_progress_ = false; + bool needs_restart_ = false; + std::unique_ptr bundle_loader_; + scoped_refptr file_task_runner_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(BraveWidevineBundleManager); +}; + +#endif // BRAVE_BROWSER_WIDEVINE_BRAVE_WIDEVINE_BUNDLE_MANAGER_H_ diff --git a/browser/widevine/brave_widevine_bundle_manager_unittest.cc b/browser/widevine/brave_widevine_bundle_manager_unittest.cc new file mode 100644 index 000000000000..4db0edda5756 --- /dev/null +++ b/browser/widevine/brave_widevine_bundle_manager_unittest.cc @@ -0,0 +1,248 @@ +/* Copyright (c) 2019 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/widevine/brave_widevine_bundle_manager.h" + +#include "base/files/scoped_temp_dir.h" +#include "brave/common/pref_names.h" +#include "brave/grit/brave_generated_resources.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "chrome/test/base/testing_profile_manager.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/cdm_registry.h" +#include "content/public/common/cdm_info.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "content/test/test_content_client.h" +#include "media/base/decrypt_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/widevine/cdm/widevine_cdm_common.h" +#include "widevine_cdm_version.h" + +class TestClient : public content::TestContentClient { + public: + TestClient() {} + ~TestClient() override {} + + void AddContentDecryptionModules( + std::vector* cdms, + std::vector* cdm_host_file_paths) override { + // Clear at every test case. + // If not, previously set info is remained because CdmRegistry is global + // instance. + cdms->clear(); + + if (empty_cdms_) return; + + content::CdmCapability capability; + capability.encryption_schemes.insert(media::EncryptionMode::kCenc); + cdms->push_back( + content::CdmInfo(std::string(), base::Token(), base::Version(), + base::FilePath(), std::string(), + capability, kWidevineKeySystem, false)); + } + + void set_empty_cdms(bool empty) { empty_cdms_ = empty; } + + bool empty_cdms_ = true; +}; + +class BraveWidevineBundleManagerTest : public testing::Test { + public: + BraveWidevineBundleManagerTest() + : testing_profile_manager_(TestingBrowserProcess::GetGlobal()) { + } + ~BraveWidevineBundleManagerTest() override {} + + protected: + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(testing_profile_manager_.SetUp(temp_dir_.GetPath())); + manager_.is_test_ = true; + } + + void PrepareCdmRegistry(bool empty_cdms) { + client_.set_empty_cdms(empty_cdms); + SetContentClient(&client_); + content::CdmRegistry::GetInstance()->Init(); + } + + void PreparePrefs() { + auto* registry = static_cast( + pref_service()->DeprecatedGetPrefRegistry()); + registry->RegisterBooleanPref(kWidevineOptedIn, false); + BraveWidevineBundleManager::RegisterProfilePrefs(registry); + } + + void PrepareTest(bool empty_cdms) { + PreparePrefs(); + PrepareCdmRegistry(empty_cdms); + } + + PrefService* pref_service() { + return ProfileManager::GetActiveUserProfile()->GetPrefs(); + } + + void CheckPrefsStatesAreInitialState() { + DCHECK_EQ(false, pref_service()->GetBoolean(kWidevineOptedIn)); + DCHECK_EQ(BraveWidevineBundleManager::kWidevineInvalidVersion, + pref_service()->GetString(kWidevineInstalledVersion)); + } + + void CheckPrefsStatesAreInstalledState() { + DCHECK_EQ(true, pref_service()->GetBoolean(kWidevineOptedIn)); + DCHECK_EQ(WIDEVINE_CDM_VERSION_STRING, + pref_service()->GetString(kWidevineInstalledVersion)); + } + + content::TestBrowserThreadBundle threads_; + BraveWidevineBundleManager manager_; + TestingProfileManager testing_profile_manager_; + base::ScopedTempDir temp_dir_; + TestClient client_; +}; + +TEST_F(BraveWidevineBundleManagerTest, InitialPrefsest) { + PrepareTest(true); + + CheckPrefsStatesAreInitialState(); +} + +TEST_F(BraveWidevineBundleManagerTest, InitialWithCdmResteredTest) { + PrepareTest(false); + + CheckPrefsStatesAreInitialState(); + + // When widevine is registered, prefs are restored. + manager_.StartupCheck(); + CheckPrefsStatesAreInstalledState(); +} + +TEST_F(BraveWidevineBundleManagerTest, PrefsResetTestWithEmptyCdmRegistry) { + PrepareTest(true); + + // When only prefs are set w/o cdm library, reset prefs to initial state. + pref_service()->SetBoolean(kWidevineOptedIn, true); + pref_service()->SetString(kWidevineInstalledVersion, + WIDEVINE_CDM_VERSION_STRING); + + manager_.StartupCheck(); + CheckPrefsStatesAreInitialState(); +} + +TEST_F(BraveWidevineBundleManagerTest, InProgressTest) { + PrepareTest(true); + + manager_.StartupCheck(); + manager_.InstallWidevineBundle(base::BindOnce([](const std::string&) {}), + false); + DCHECK(manager_.in_progress()); + + manager_.InstallDone(""); + DCHECK(!manager_.in_progress()); +} + +TEST_F(BraveWidevineBundleManagerTest, InstallSuccessTest) { + PrepareTest(true); + + DCHECK(!manager_.needs_restart()); + + manager_.StartupCheck(); + manager_.InstallWidevineBundle(base::BindOnce([](const std::string&) {}), + false); + + manager_.InstallDone(""); + + DCHECK(manager_.needs_restart()); + CheckPrefsStatesAreInitialState(); + + manager_.WillRestart(); + CheckPrefsStatesAreInstalledState(); +} + +TEST_F(BraveWidevineBundleManagerTest, RetryInstallAfterFail) { + PrepareTest(true); + + DCHECK(!manager_.needs_restart()); + manager_.StartupCheck(); + manager_.InstallWidevineBundle(base::BindOnce([](const std::string&) {}), + false); + + manager_.InstallDone("failed"); + + DCHECK(!manager_.needs_restart()); + CheckPrefsStatesAreInitialState(); + + // Check request install again goes in-progress state. + manager_.InstallWidevineBundle(base::BindOnce([](const std::string&) {}), + false); + DCHECK(manager_.in_progress()); +} + +TEST_F(BraveWidevineBundleManagerTest, DownloadFailTest) { + PrepareTest(true); + + manager_.StartupCheck(); + manager_.InstallWidevineBundle(base::BindOnce([](const std::string&) {}), + false); + DCHECK(manager_.in_progress()); + + // Empty path means download fail. + manager_.OnBundleDownloaded(base::FilePath()); + DCHECK(!manager_.in_progress()); + CheckPrefsStatesAreInitialState(); +} + +TEST_F(BraveWidevineBundleManagerTest, UnzipFailTest) { + PrepareTest(true); + + manager_.StartupCheck(); + manager_.InstallWidevineBundle(base::BindOnce([](const std::string&) {}), + false); + DCHECK(manager_.in_progress()); + + manager_.OnBundleUnzipped("unzip failed"); + DCHECK(!manager_.in_progress()); + CheckPrefsStatesAreInitialState(); +} + +TEST_F(BraveWidevineBundleManagerTest, UpdateTriggerTest) { + PrepareTest(false); + + // Set installed state with different version to trigger update. + pref_service()->SetBoolean(kWidevineOptedIn, true); + pref_service()->SetString(kWidevineInstalledVersion, "1.0.0.0"); + + DCHECK(!manager_.update_requested_); + + manager_.StartupCheck(); + DCHECK(manager_.update_requested_); +} + +TEST_F(BraveWidevineBundleManagerTest, MessageStringTest) { + PrepareTest(true); + + DCHECK(!manager_.needs_restart()); + DCHECK_EQ(IDS_NOT_INSTALLED_WIDEVINE_TITLE, + manager_.GetWidevineContentSettingsBubbleTitleText()); + DCHECK_EQ(IDS_WIDEVINE_INSTALL, + manager_.GetWidevineContentSettingsBubbleLinkText()); + DCHECK_EQ(IDS_WIDEVINE_NOT_INSTALLED_MESSAGE, + manager_.GetWidevineBlockedImageMessage()); + DCHECK_EQ(IDS_WIDEVINE_NOT_INSTALLED_EXPLANATORY_TEXT, + manager_.GetWidevineBlockedImageTooltip()); + + manager_.set_needs_restart(true); + DCHECK_EQ(IDS_NOT_ENABLED_WIDEVINE_TITLE, + manager_.GetWidevineContentSettingsBubbleTitleText()); + DCHECK_EQ(IDS_WIDEVINE_RESTART_BROWSER, + manager_.GetWidevineContentSettingsBubbleLinkText()); + DCHECK_EQ(IDS_WIDEVINE_NOT_ENABLED_MESSAGE, + manager_.GetWidevineBlockedImageMessage()); + DCHECK_EQ(IDS_WIDEVINE_NOT_ENABLED_EXPLANATORY_TEXT, + manager_.GetWidevineBlockedImageTooltip()); +} diff --git a/browser/widevine/brave_widevine_bundle_unzipper.cc b/browser/widevine/brave_widevine_bundle_unzipper.cc new file mode 100644 index 000000000000..60b35acfc2f7 --- /dev/null +++ b/browser/widevine/brave_widevine_bundle_unzipper.cc @@ -0,0 +1,146 @@ +/* Copyright (c) 2019 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/widevine/brave_widevine_bundle_unzipper.h" + +#include + +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/native_library.h" +#include "components/services/unzip/public/cpp/unzip.h" +#include "content/public/browser/browser_thread.h" +#include "services/service_manager/public/cpp/connector.h" +#include "third_party/widevine/cdm/widevine_cdm_common.h" + +namespace { +// This is filter function for unzipper. We only unzip cdm library. +// Returns true if |file_path| widevine cdm library name. +bool IsWidevineCdmFile(const base::FilePath& file_path) { + CHECK(!file_path.IsAbsolute()); + return base::FilePath::CompareEqualIgnoreCase( + file_path.value(), + base::GetNativeLibraryName(kWidevineCdmLibraryName)); +} + +base::Optional GetTempDirForUnzip() { + base::FilePath unzip_dir; + if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &unzip_dir)) + return base::Optional(); + + return unzip_dir; +} + +} // namespace + +// static +scoped_refptr +BraveWidevineBundleUnzipper::Create( + service_manager::Connector* connector, + scoped_refptr file_task_runner, + DoneCallback done_callback) { + DCHECK(connector); + DCHECK(file_task_runner); + DCHECK(done_callback); + return base::WrapRefCounted( + new BraveWidevineBundleUnzipper(connector, + file_task_runner, + std::move(done_callback))); +} + +void BraveWidevineBundleUnzipper::LoadFromZipFileInDir( + const base::FilePath& zipped_bundle_file, + const base::FilePath& unzip_dir, + bool delete_file) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + DCHECK(!zipped_bundle_file.empty()); + DCHECK(!unzip_dir.empty()); + DVLOG(1) << __func__ << ": zipped bundle file: " << zipped_bundle_file; + DVLOG(1) << __func__ << ": target install dir: " << unzip_dir; + + delete_zip_file_ = delete_file; + target_unzip_dir_ = unzip_dir; + zipped_bundle_file_ = zipped_bundle_file; + + base::PostTaskAndReplyWithResult( + file_task_runner_.get(), + FROM_HERE, + base::BindOnce(&GetTempDirForUnzip), + base::BindOnce(&BraveWidevineBundleUnzipper::OnGetTempDirForUnzip, this)); +} + +void BraveWidevineBundleUnzipper::OnGetTempDirForUnzip( + base::Optional temp_unzip_dir) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (!temp_unzip_dir) { + UnzipDone("Getting temp dir for unzip failed"); + return; + } + + temp_unzip_dir_ = *temp_unzip_dir; + + DVLOG(1) << __func__ << ": temp unzip dir: " << temp_unzip_dir_; + + unzip::UnzipWithFilter( + connector_->Clone(), zipped_bundle_file_, *temp_unzip_dir, + base::BindRepeating(&IsWidevineCdmFile), + base::BindOnce(&BraveWidevineBundleUnzipper::OnUnzippedInTempDir, this)); +} + +void BraveWidevineBundleUnzipper::OnUnzippedInTempDir(bool status) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (!status) { + UnzipDone("Unzip failed"); + return; + } + base::PostTaskAndReplyWithResult( + file_task_runner_.get(), + FROM_HERE, + base::BindOnce( + &BraveWidevineBundleUnzipper::MoveUnzippedLibFromTempToTargetDir, + this), + base::BindOnce(&BraveWidevineBundleUnzipper::UnzipDone, this)); +} + +std::string BraveWidevineBundleUnzipper::MoveUnzippedLibFromTempToTargetDir() { + const base::FilePath source = temp_unzip_dir_.AppendASCII( + base::GetNativeLibraryName(kWidevineCdmLibraryName)); + DCHECK(base::PathExists(source)); + + const base::FilePath target = target_unzip_dir_.AppendASCII( + base::GetNativeLibraryName(kWidevineCdmLibraryName)); + + std::string error; + if (!base::Move(source, target)) + error = "widevine lib move failed"; + + base::DeleteFile(temp_unzip_dir_, true); + + if (delete_zip_file_) + base::DeleteFile(zipped_bundle_file_, false); + + return error; +} + +void BraveWidevineBundleUnzipper::UnzipDone(const std::string& error) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + std::move(done_callback_).Run(error); +} + +BraveWidevineBundleUnzipper::BraveWidevineBundleUnzipper( + service_manager::Connector* connector, + scoped_refptr file_task_runner, + DoneCallback done_callback) + : connector_(connector), + file_task_runner_(file_task_runner), + done_callback_(std::move(done_callback)) { +} + +BraveWidevineBundleUnzipper::~BraveWidevineBundleUnzipper() { +} diff --git a/browser/widevine/brave_widevine_bundle_unzipper.h b/browser/widevine/brave_widevine_bundle_unzipper.h new file mode 100644 index 000000000000..a7c101a4662f --- /dev/null +++ b/browser/widevine/brave_widevine_bundle_unzipper.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2019 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_WIDEVINE_BRAVE_WIDEVINE_BUNDLE_UNZIPPER_H_ +#define BRAVE_BROWSER_WIDEVINE_BRAVE_WIDEVINE_BUNDLE_UNZIPPER_H_ + +#include + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/optional.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace service_manager { +class Connector; +} + +class BraveWidevineBundleUnzipper + : public base::RefCountedThreadSafe { + public: + using DoneCallback = base::OnceCallback; + + static scoped_refptr Create( + service_manager::Connector* connector, + scoped_refptr file_task_runner, + DoneCallback done_callback); + + // Unzips widevine cdm library of |zipped_bundle_file| into |unzip_dir|. + // This will remove passed |zipped_bundle_file| if |delete_file| is true. + void LoadFromZipFileInDir(const base::FilePath& zipped_bundle_file, + const base::FilePath& unzip_dir, + bool delete_file); + + private: + friend class base::RefCountedThreadSafe; + + BraveWidevineBundleUnzipper( + service_manager::Connector* connector, + scoped_refptr file_task_runner, + DoneCallback done_callback); + ~BraveWidevineBundleUnzipper(); + + void OnGetTempDirForUnzip(base::Optional temp_unzip_dir); + void OnUnzippedInTempDir(bool status); + + std::string MoveUnzippedLibFromTempToTargetDir(); + + // Called all steps are fininshed. + // |error| is not empty if there is an error during the unzip. + void UnzipDone(const std::string& error); + + bool delete_zip_file_ = false; + base::FilePath zipped_bundle_file_; + base::FilePath target_unzip_dir_; + + // Temp dir is used because unzipper failed when target file is + // already existed. This can be happened when widevine lib is updated. + // So, lib is extracted to temp dir then copied to |target_unzip_dir_|. + base::FilePath temp_unzip_dir_; + + service_manager::Connector* connector_; + scoped_refptr file_task_runner_; + DoneCallback done_callback_; + + DISALLOW_COPY_AND_ASSIGN(BraveWidevineBundleUnzipper); +}; + +#endif // BRAVE_BROWSER_WIDEVINE_BRAVE_WIDEVINE_BUNDLE_UNZIPPER_H_ diff --git a/common/brave_paths.cc b/common/brave_paths.cc index 5e5e26f0e954..1d1a46eecdff 100644 --- a/common/brave_paths.cc +++ b/common/brave_paths.cc @@ -10,6 +10,13 @@ #include "base/logging.h" #include "base/path_service.h" #include "build/build_config.h" +#include "third_party/widevine/cdm/buildflags.h" + +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) +#include "base/native_library.h" +#include "chrome/common/chrome_paths.h" +#include "third_party/widevine/cdm/widevine_cdm_common.h" +#endif namespace brave { @@ -39,4 +46,21 @@ void RegisterPathProvider() { base::PathService::RegisterProvider(PathProvider, PATH_START, PATH_END); } +void OverridePath() { +#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) + // Brave downloads cdm lib to user dir when user accepts instead of shippig by + // default. So, override |FILE_WIDEVINE_CDM| to new path in user dir. + base::FilePath widevine_cdm_path; + if (base::PathService::Get(chrome::DIR_USER_DATA, &widevine_cdm_path)) { + widevine_cdm_path = + widevine_cdm_path.AppendASCII(kWidevineCdmBaseDirectory) + .AppendASCII(base::GetNativeLibraryName(kWidevineCdmLibraryName)); + base::PathService::OverrideAndCreateIfNeeded(chrome::FILE_WIDEVINE_CDM, + widevine_cdm_path, + true, + false); + } +#endif +} + } // namespace brave diff --git a/common/brave_paths.h b/common/brave_paths.h index 38dc36e63c28..ace52ec850ae 100644 --- a/common/brave_paths.h +++ b/common/brave_paths.h @@ -22,6 +22,9 @@ enum { // Call once to register the provider for the path keys defined above. void RegisterPathProvider(); +// Call once to override chrome's path with brave's new path. +void OverridePath(); + } // namespace brave -#endif // BRAVE_COMMON_BRAVE_PATHS_H \ No newline at end of file +#endif // BRAVE_COMMON_BRAVE_PATHS_H diff --git a/common/pref_names.cc b/common/pref_names.cc index e1ac730a3fb5..3d6e62af74ad 100644 --- a/common/pref_names.cc +++ b/common/pref_names.cc @@ -17,6 +17,7 @@ const char kFirstCheckMade[] = "brave.stats.first_check_made"; const char kWeekOfInstallation[] = "brave.stats.week_of_installation"; const char kAdBlockCurrentRegion[] = "brave.ad_block.current_region"; const char kWidevineOptedIn[] = "brave.widevine_opted_in"; +const char kWidevineInstalledVersion[] = "brave.widevine_installed_version"; const char kUseAlternativeSearchEngineProvider[] = "brave.use_alternate_private_search_engine"; const char kAlternativeSearchEngineProviderInTor[] = diff --git a/common/pref_names.h b/common/pref_names.h index 880c8270a51e..598bd70f735c 100644 --- a/common/pref_names.h +++ b/common/pref_names.h @@ -18,6 +18,7 @@ extern const char kFirstCheckMade[]; extern const char kWeekOfInstallation[]; extern const char kAdBlockCurrentRegion[]; extern const char kWidevineOptedIn[]; +extern const char kWidevineInstalledVersion[]; extern const char kUseAlternativeSearchEngineProvider[]; extern const char kAlternativeSearchEngineProviderInTor[]; extern const char kBraveThemeType[]; diff --git a/patches/third_party-widevine-cdm-widevine.gni.patch b/patches/third_party-widevine-cdm-widevine.gni.patch new file mode 100644 index 000000000000..9ccd2802d78f --- /dev/null +++ b/patches/third_party-widevine-cdm-widevine.gni.patch @@ -0,0 +1,14 @@ +diff --git a/third_party/widevine/cdm/widevine.gni b/third_party/widevine/cdm/widevine.gni +index e10d9b9b8d2975a731adfee2eb0086afd7975f97..10a72c2511fb99724a1c96bf1fd0f2400a5ff930 100644 +--- a/third_party/widevine/cdm/widevine.gni ++++ b/third_party/widevine/cdm/widevine.gni +@@ -35,6 +35,9 @@ enable_widevine_cdm_component = + + # Widevine CDM is bundled as part of Google Chrome builds. + bundle_widevine_cdm = enable_library_widevine_cdm && is_chrome_branded ++if (brave_chromium_build) { ++ bundle_widevine_cdm = enable_library_widevine_cdm && is_desktop_linux ++} + + enable_widevine_cdm_host_verification = + enable_library_widevine_cdm && enable_cdm_host_verification diff --git a/test/BUILD.gn b/test/BUILD.gn index e551f46a6432..cfbdd00235e6 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -152,6 +152,13 @@ test("brave_unit_tests") { ] } + if (bundle_widevine_cdm) { + sources += [ + "//brave/browser/widevine/brave_widevine_bundle_manager_unittest.cc", + ] + deps += [ "//third_party/widevine/cdm:headers" ] + } + public_deps = [ "//base", "//base/test:test_support",