blob: 769a38ba4a2863d75f54a89aa4e936d021c265ed [file] [log] [blame]
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/component_updater/widevine_cdm_component_installer.h"
6
7#include <string.h>
8
9#include <vector>
10
11#include "base/base_paths.h"
12#include "base/bind.h"
13#include "base/command_line.h"
14#include "base/compiler_specific.h"
15#include "base/file_util.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010016#include "base/files/file_enumerator.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010017#include "base/files/file_path.h"
18#include "base/logging.h"
19#include "base/path_service.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010020#include "base/strings/string_util.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010021#include "base/values.h"
22#include "base/version.h"
23#include "build/build_config.h"
24#include "chrome/browser/component_updater/component_updater_service.h"
25#include "chrome/browser/plugins/plugin_prefs.h"
26#include "chrome/common/chrome_constants.h"
27#include "chrome/common/chrome_paths.h"
28#include "chrome/common/widevine_cdm_constants.h"
29#include "content/public/browser/browser_thread.h"
30#include "content/public/browser/plugin_service.h"
31#include "content/public/common/pepper_plugin_info.h"
32#include "third_party/widevine/cdm/widevine_cdm_common.h"
33
34#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR.
35
36using content::BrowserThread;
37using content::PluginService;
38
39namespace {
40
41// TODO(xhwang): Move duplicate code among all component installer
42// implementations to some common place.
43
44#if defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
45
Ben Murdochbb1529c2013-08-08 10:24:53 +010046// CRX hash. The extension id is: oimompecagnajdejgnnjijobebaeigek.
47const uint8 kSha2Hash[] = { 0xe8, 0xce, 0xcf, 0x42, 0x06, 0xd0, 0x93, 0x49,
48 0x6d, 0xd9, 0x89, 0xe1, 0x41, 0x04, 0x86, 0x4a,
49 0x8f, 0xbd, 0x86, 0x12, 0xb9, 0x58, 0x9b, 0xfb,
50 0x4f, 0xbb, 0x1b, 0xa9, 0xd3, 0x85, 0x37, 0xef };
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010051
52// File name of the Widevine CDM component manifest on different platforms.
53const char kWidevineCdmManifestName[] = "WidevineCdm";
54
55// Name of the Widevine CDM OS in the component manifest.
Ben Murdochbb1529c2013-08-08 10:24:53 +010056const char kWidevineCdmPlatform[] =
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010057#if defined(OS_MACOSX)
58 "mac";
59#elif defined(OS_WIN)
60 "win";
61#else // OS_LINUX, etc. TODO(viettrungluu): Separate out Chrome OS and Android?
62 "linux";
63#endif
64
65// Name of the Widevine CDM architecture in the component manifest.
66const char kWidevineCdmArch[] =
67#if defined(ARCH_CPU_X86)
Ben Murdochbb1529c2013-08-08 10:24:53 +010068 "x86";
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010069#elif defined(ARCH_CPU_X86_64)
70 "x64";
71#else // TODO(viettrungluu): Support an ARM check?
72 "???";
73#endif
74
75// If we don't have a Widevine CDM component, this is the version we claim.
76const char kNullVersion[] = "0.0.0.0";
77
78// The base directory on Windows looks like:
79// <profile>\AppData\Local\Google\Chrome\User Data\WidevineCdm\.
80base::FilePath GetWidevineCdmBaseDirectory() {
81 base::FilePath result;
82 PathService::Get(chrome::DIR_COMPONENT_WIDEVINE_CDM, &result);
83 return result;
84}
85
Ben Murdochbb1529c2013-08-08 10:24:53 +010086// Widevine CDM is packaged as a multi-CRX. Widevine CDM binaries are located in
87// _platform_specific/<platform_arch> folder in the package. This function
88// returns the platform-specific subdirectory that is part of that multi-CRX.
89base::FilePath GetPlatformDirectory(const base::FilePath& base_path) {
90 std::string platform_arch = kWidevineCdmPlatform;
91 platform_arch += '_';
92 platform_arch += kWidevineCdmArch;
93 return base_path.AppendASCII("_platform_specific").AppendASCII(platform_arch);
94}
95
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010096// Widevine CDM has the version encoded in the path so we need to enumerate the
97// directories to find the full path.
98// On success, |latest_dir| returns something like:
99// <profile>\AppData\Local\Google\Chrome\User Data\WidevineCdm\10.3.44.555\.
100// |latest_version| returns the corresponding version number. |older_dirs|
101// returns directories of all older versions.
102bool GetWidevineCdmDirectory(base::FilePath* latest_dir,
103 base::Version* latest_version,
104 std::vector<base::FilePath>* older_dirs) {
105 base::FilePath base_dir = GetWidevineCdmBaseDirectory();
106 bool found = false;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100107 base::FileEnumerator file_enumerator(
108 base_dir, false, base::FileEnumerator::DIRECTORIES);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100109 for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
110 path = file_enumerator.Next()) {
111 base::Version version(path.BaseName().MaybeAsASCII());
112 if (!version.IsValid())
113 continue;
114 if (found) {
115 if (version.CompareTo(*latest_version) > 0) {
116 older_dirs->push_back(*latest_dir);
117 *latest_dir = path;
118 *latest_version = version;
119 } else {
120 older_dirs->push_back(path);
121 }
122 } else {
123 *latest_dir = path;
124 *latest_version = version;
125 found = true;
126 }
127 }
128 return found;
129}
130
131bool MakeWidevineCdmPluginInfo(const base::FilePath& path,
132 const base::Version& version,
133 content::PepperPluginInfo* plugin_info) {
134 if (!version.IsValid() ||
135 version.components().size() !=
136 static_cast<size_t>(kWidevineCdmVersionNumComponents)) {
137 return false;
138 }
139
140 plugin_info->is_internal = false;
141 // Widevine CDM must run out of process.
142 plugin_info->is_out_of_process = true;
143 plugin_info->path = path;
144 plugin_info->name = kWidevineCdmDisplayName;
145 plugin_info->description = kWidevineCdmDescription;
146 plugin_info->version = version.GetString();
Ben Murdochca12bfa2013-07-23 11:17:05 +0100147 content::WebPluginMimeType widevine_cdm_mime_type(
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100148 kWidevineCdmPluginMimeType,
149 kWidevineCdmPluginExtension,
150 kWidevineCdmPluginMimeTypeDescription);
151 plugin_info->mime_types.push_back(widevine_cdm_mime_type);
152 plugin_info->permissions = kWidevineCdmPluginPermissions;
153
154 return true;
155}
156
157void RegisterWidevineCdmWithChrome(const base::FilePath& path,
158 const base::Version& version) {
159 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
160 content::PepperPluginInfo plugin_info;
161 if (!MakeWidevineCdmPluginInfo(path, version, &plugin_info))
162 return;
163
164 PluginService::GetInstance()->RegisterInternalPlugin(
165 plugin_info.ToWebPluginInfo(), true);
166 PluginService::GetInstance()->RefreshPlugins();
167}
168
169// Returns true if this browser is compatible with the given Widevine CDM
170// manifest, with the version specified in the manifest in |version_out|.
171bool CheckWidevineCdmManifest(const base::DictionaryValue& manifest,
172 base::Version* version_out) {
173 std::string name;
174 manifest.GetStringASCII("name", &name);
175
176 if (name != kWidevineCdmManifestName)
177 return false;
178
179 std::string proposed_version;
180 manifest.GetStringASCII("version", &proposed_version);
181 base::Version version(proposed_version.c_str());
182 if (!version.IsValid())
183 return false;
184
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100185 *version_out = version;
186 return true;
187}
188
189class WidevineCdmComponentInstaller : public ComponentInstaller {
190 public:
191 explicit WidevineCdmComponentInstaller(const base::Version& version);
192 virtual ~WidevineCdmComponentInstaller() {}
193
194 virtual void OnUpdateError(int error) OVERRIDE;
195 virtual bool Install(const base::DictionaryValue& manifest,
196 const base::FilePath& unpack_path) OVERRIDE;
197
Ben Murdocheb525c52013-07-10 11:40:50 +0100198 virtual bool GetInstalledFile(const std::string& file,
199 base::FilePath* installed_file) OVERRIDE;
200
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100201 private:
202 base::Version current_version_;
203};
204
205WidevineCdmComponentInstaller::WidevineCdmComponentInstaller(
206 const base::Version& version)
207 : current_version_(version) {
208 DCHECK(version.IsValid());
209}
210
211void WidevineCdmComponentInstaller::OnUpdateError(int error) {
212 NOTREACHED() << "Widevine CDM update error: " << error;
213}
214
215bool WidevineCdmComponentInstaller::Install(
216 const base::DictionaryValue& manifest,
217 const base::FilePath& unpack_path) {
218 base::Version version;
219 if (!CheckWidevineCdmManifest(manifest, &version))
220 return false;
221 if (current_version_.CompareTo(version) > 0)
222 return false;
223
Ben Murdochbb1529c2013-08-08 10:24:53 +0100224 if (!base::PathExists(
225 GetPlatformDirectory(unpack_path).AppendASCII(kWidevineCdmFileName))) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100226 return false;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100227 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100228
229 base::FilePath adapter_source_path;
230 PathService::Get(chrome::FILE_WIDEVINE_CDM_ADAPTER, &adapter_source_path);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100231 if (!base::PathExists(adapter_source_path))
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100232 return false;
233
234 // Passed the basic tests. Time to install it.
235 base::FilePath install_path =
236 GetWidevineCdmBaseDirectory().AppendASCII(version.GetString());
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100237 if (base::PathExists(install_path))
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100238 return false;
Ben Murdocheb525c52013-07-10 11:40:50 +0100239 if (!base::Move(unpack_path, install_path))
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100240 return false;
241
Ben Murdochbb1529c2013-08-08 10:24:53 +0100242 base::FilePath adapter_install_path = GetPlatformDirectory(install_path)
243 .AppendASCII(kWidevineCdmAdapterFileName);
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100244 if (!base::CopyFile(adapter_source_path, adapter_install_path))
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100245 return false;
246
247 // Installation is done. Now register the Widevine CDM with chrome.
248 current_version_ = version;
249 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
250 &RegisterWidevineCdmWithChrome, adapter_install_path, version));
251 return true;
252}
253
Ben Murdochbb1529c2013-08-08 10:24:53 +0100254// Given |file|, a path like "_platform_specific/win_x86/widevinecdm.dll",
255// returns the assumed install path. The path separator in |file| is '/'
256// for all platforms. Caller is responsible for checking that the
257// |installed_file| actually exists.
Ben Murdocheb525c52013-07-10 11:40:50 +0100258bool WidevineCdmComponentInstaller::GetInstalledFile(
259 const std::string& file, base::FilePath* installed_file) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100260 if (current_version_.Equals(base::Version(kNullVersion)))
261 return false; // No CDM has been installed yet.
262
Ben Murdochbb1529c2013-08-08 10:24:53 +0100263 *installed_file = GetWidevineCdmBaseDirectory().AppendASCII(
264 current_version_.GetString()).AppendASCII(file);
Ben Murdocheb525c52013-07-10 11:40:50 +0100265 return true;
266}
267
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100268void FinishWidevineCdmUpdateRegistration(ComponentUpdateService* cus,
269 const base::Version& version) {
270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
271 CrxComponent widevine_cdm;
272 widevine_cdm.name = "WidevineCdm";
273 widevine_cdm.installer = new WidevineCdmComponentInstaller(version);
274 widevine_cdm.version = version;
275 widevine_cdm.pk_hash.assign(kSha2Hash, &kSha2Hash[sizeof(kSha2Hash)]);
276 if (cus->RegisterComponent(widevine_cdm) != ComponentUpdateService::kOk) {
277 NOTREACHED() << "Widevine CDM component registration failed.";
278 return;
279 }
280}
281
282void StartWidevineCdmUpdateRegistration(ComponentUpdateService* cus) {
283 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100284 base::FilePath base_dir = GetWidevineCdmBaseDirectory();
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100285 if (!base::PathExists(base_dir) &&
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100286 !file_util::CreateDirectory(base_dir)) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100287 NOTREACHED() << "Could not create Widevine CDM directory.";
288 return;
289 }
290
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100291 base::FilePath latest_dir;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100292 base::Version version(kNullVersion);
293 std::vector<base::FilePath> older_dirs;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100294
295 if (GetWidevineCdmDirectory(&latest_dir, &version, &older_dirs)) {
Ben Murdochbb1529c2013-08-08 10:24:53 +0100296 base::FilePath latest_platform_dir = GetPlatformDirectory(latest_dir);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100297 base::FilePath adapter_path =
Ben Murdochbb1529c2013-08-08 10:24:53 +0100298 latest_platform_dir.AppendASCII(kWidevineCdmAdapterFileName);
299 base::FilePath cdm_path =
300 latest_platform_dir.AppendASCII(kWidevineCdmFileName);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100301
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100302 if (base::PathExists(adapter_path) &&
303 base::PathExists(cdm_path)) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100304 BrowserThread::PostTask(
305 BrowserThread::UI, FROM_HERE,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100306 base::Bind(&RegisterWidevineCdmWithChrome, adapter_path, version));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100307 } else {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100308 base::DeleteFile(latest_dir, true);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100309 version = base::Version(kNullVersion);
310 }
311 }
312
313 BrowserThread::PostTask(
314 BrowserThread::UI, FROM_HERE,
315 base::Bind(&FinishWidevineCdmUpdateRegistration, cus, version));
316
317 // Remove older versions of Widevine CDM.
318 for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
319 iter != older_dirs.end(); ++iter) {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100320 base::DeleteFile(*iter, true);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100321 }
322}
323
324#endif // defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
325
326} // namespace
327
328void RegisterWidevineCdmComponent(ComponentUpdateService* cus) {
329#if defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
330 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
331 base::Bind(&StartWidevineCdmUpdateRegistration, cus));
332#endif // defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
333}