widevine_cdm_component_installer.cc revision bb1529ce867d8845a77ec7cdf3e3003ef1771a40
1// 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"
16#include "base/files/file_enumerator.h"
17#include "base/files/file_path.h"
18#include "base/logging.h"
19#include "base/path_service.h"
20#include "base/strings/string_util.h"
21#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
46// 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 };
51
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.
56const char kWidevineCdmPlatform[] =
57#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)
68    "x86";
69#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
86// 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
96// 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;
107  base::FileEnumerator file_enumerator(
108      base_dir, false, base::FileEnumerator::DIRECTORIES);
109  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();
147  content::WebPluginMimeType widevine_cdm_mime_type(
148      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
185  *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
198  virtual bool GetInstalledFile(const std::string& file,
199                                base::FilePath* installed_file) OVERRIDE;
200
201 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
224  if (!base::PathExists(
225      GetPlatformDirectory(unpack_path).AppendASCII(kWidevineCdmFileName))) {
226    return false;
227  }
228
229  base::FilePath adapter_source_path;
230  PathService::Get(chrome::FILE_WIDEVINE_CDM_ADAPTER, &adapter_source_path);
231  if (!base::PathExists(adapter_source_path))
232    return false;
233
234  // Passed the basic tests. Time to install it.
235  base::FilePath install_path =
236      GetWidevineCdmBaseDirectory().AppendASCII(version.GetString());
237  if (base::PathExists(install_path))
238    return false;
239  if (!base::Move(unpack_path, install_path))
240    return false;
241
242  base::FilePath adapter_install_path = GetPlatformDirectory(install_path)
243      .AppendASCII(kWidevineCdmAdapterFileName);
244  if (!base::CopyFile(adapter_source_path, adapter_install_path))
245    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
254// 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.
258bool WidevineCdmComponentInstaller::GetInstalledFile(
259    const std::string& file, base::FilePath* installed_file) {
260  if (current_version_.Equals(base::Version(kNullVersion)))
261    return false;  // No CDM has been installed yet.
262
263  *installed_file = GetWidevineCdmBaseDirectory().AppendASCII(
264      current_version_.GetString()).AppendASCII(file);
265  return true;
266}
267
268void 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));
284  base::FilePath base_dir = GetWidevineCdmBaseDirectory();
285  if (!base::PathExists(base_dir) &&
286      !file_util::CreateDirectory(base_dir)) {
287    NOTREACHED() << "Could not create Widevine CDM directory.";
288    return;
289  }
290
291  base::FilePath latest_dir;
292  base::Version version(kNullVersion);
293  std::vector<base::FilePath> older_dirs;
294
295  if (GetWidevineCdmDirectory(&latest_dir, &version, &older_dirs)) {
296    base::FilePath latest_platform_dir = GetPlatformDirectory(latest_dir);
297    base::FilePath adapter_path =
298        latest_platform_dir.AppendASCII(kWidevineCdmAdapterFileName);
299    base::FilePath cdm_path =
300        latest_platform_dir.AppendASCII(kWidevineCdmFileName);
301
302    if (base::PathExists(adapter_path) &&
303        base::PathExists(cdm_path)) {
304      BrowserThread::PostTask(
305          BrowserThread::UI, FROM_HERE,
306          base::Bind(&RegisterWidevineCdmWithChrome, adapter_path, version));
307    } else {
308      base::DeleteFile(latest_dir, true);
309      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) {
320    base::DeleteFile(*iter, true);
321  }
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}
334