widevine_cdm_component_installer.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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: pdkaonnflpjcgibpgaanildgengnihcm.
47const uint8 kSha2Hash[] = { 0xf3, 0xa0, 0xed, 0xd5, 0xbf, 0x92, 0x68, 0x1f,
48                            0x60, 0x0d, 0x8b, 0x36, 0x4d, 0x6d, 0x87, 0x2c,
49                            0x86, 0x61, 0x12, 0x20, 0x21, 0xf8, 0x94, 0xdd,
50                            0xe1, 0xb6, 0xb4, 0x55, 0x34, 0x8c, 0x2e, 0x20 };
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 kWidevineCdmOperatingSystem[] =
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    "ia32";
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 has the version encoded in the path so we need to enumerate the
87// directories to find the full path.
88// On success, |latest_dir| returns something like:
89// <profile>\AppData\Local\Google\Chrome\User Data\WidevineCdm\10.3.44.555\.
90// |latest_version| returns the corresponding version number. |older_dirs|
91// returns directories of all older versions.
92bool GetWidevineCdmDirectory(base::FilePath* latest_dir,
93                             base::Version* latest_version,
94                             std::vector<base::FilePath>* older_dirs) {
95  base::FilePath base_dir = GetWidevineCdmBaseDirectory();
96  bool found = false;
97  base::FileEnumerator file_enumerator(
98      base_dir, false, base::FileEnumerator::DIRECTORIES);
99  for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
100       path = file_enumerator.Next()) {
101    base::Version version(path.BaseName().MaybeAsASCII());
102    if (!version.IsValid())
103      continue;
104    if (found) {
105      if (version.CompareTo(*latest_version) > 0) {
106        older_dirs->push_back(*latest_dir);
107        *latest_dir = path;
108        *latest_version = version;
109      } else {
110        older_dirs->push_back(path);
111      }
112    } else {
113      *latest_dir = path;
114      *latest_version = version;
115      found = true;
116    }
117  }
118  return found;
119}
120
121bool MakeWidevineCdmPluginInfo(const base::FilePath& path,
122                               const base::Version& version,
123                               content::PepperPluginInfo* plugin_info) {
124  if (!version.IsValid() ||
125      version.components().size() !=
126          static_cast<size_t>(kWidevineCdmVersionNumComponents)) {
127    return false;
128  }
129
130  plugin_info->is_internal = false;
131  // Widevine CDM must run out of process.
132  plugin_info->is_out_of_process = true;
133  plugin_info->path = path;
134  plugin_info->name = kWidevineCdmDisplayName;
135  plugin_info->description = kWidevineCdmDescription;
136  plugin_info->version = version.GetString();
137  content::WebPluginMimeType widevine_cdm_mime_type(
138      kWidevineCdmPluginMimeType,
139      kWidevineCdmPluginExtension,
140      kWidevineCdmPluginMimeTypeDescription);
141  plugin_info->mime_types.push_back(widevine_cdm_mime_type);
142  plugin_info->permissions = kWidevineCdmPluginPermissions;
143
144  return true;
145}
146
147void RegisterWidevineCdmWithChrome(const base::FilePath& path,
148                                   const base::Version& version) {
149  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
150  content::PepperPluginInfo plugin_info;
151  if (!MakeWidevineCdmPluginInfo(path, version, &plugin_info))
152    return;
153
154  PluginService::GetInstance()->RegisterInternalPlugin(
155      plugin_info.ToWebPluginInfo(), true);
156  PluginService::GetInstance()->RefreshPlugins();
157}
158
159// Returns true if this browser is compatible with the given Widevine CDM
160// manifest, with the version specified in the manifest in |version_out|.
161bool CheckWidevineCdmManifest(const base::DictionaryValue& manifest,
162                              base::Version* version_out) {
163  std::string name;
164  manifest.GetStringASCII("name", &name);
165
166  if (name != kWidevineCdmManifestName)
167    return false;
168
169  std::string proposed_version;
170  manifest.GetStringASCII("version", &proposed_version);
171  base::Version version(proposed_version.c_str());
172  if (!version.IsValid())
173    return false;
174
175  std::string os;
176  manifest.GetStringASCII("x-widevine-cdm-os", &os);
177  if (os != kWidevineCdmOperatingSystem)
178    return false;
179
180  std::string arch;
181  manifest.GetStringASCII("x-widevine-cdm-arch", &arch);
182  if (arch != kWidevineCdmArch)
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(unpack_path.AppendASCII(kWidevineCdmFileName)))
225    return false;
226
227  base::FilePath adapter_source_path;
228  PathService::Get(chrome::FILE_WIDEVINE_CDM_ADAPTER, &adapter_source_path);
229  if (!base::PathExists(adapter_source_path))
230    return false;
231
232  // Passed the basic tests. Time to install it.
233  base::FilePath install_path =
234      GetWidevineCdmBaseDirectory().AppendASCII(version.GetString());
235  if (base::PathExists(install_path))
236    return false;
237  if (!base::Move(unpack_path, install_path))
238    return false;
239
240  base::FilePath adapter_install_path =
241      install_path.AppendASCII(kWidevineCdmAdapterFileName);
242  if (!base::CopyFile(adapter_source_path, adapter_install_path))
243    return false;
244
245  // Installation is done. Now register the Widevine CDM with chrome.
246  current_version_ = version;
247  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
248      &RegisterWidevineCdmWithChrome, adapter_install_path, version));
249  return true;
250}
251
252bool WidevineCdmComponentInstaller::GetInstalledFile(
253    const std::string& file, base::FilePath* installed_file) {
254  // Only the CDM is component-updated.
255  if (file != kWidevineCdmFileName)
256    return false;
257
258  if (current_version_.Equals(base::Version(kNullVersion)))
259    return false;  // No CDM has been installed yet.
260
261  *installed_file =
262      GetWidevineCdmBaseDirectory().AppendASCII(current_version_.GetString())
263          .AppendASCII(kWidevineCdmFileName);
264  return true;
265}
266
267void FinishWidevineCdmUpdateRegistration(ComponentUpdateService* cus,
268                                         const base::Version& version) {
269  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
270  CrxComponent widevine_cdm;
271  widevine_cdm.name = "WidevineCdm";
272  widevine_cdm.installer = new WidevineCdmComponentInstaller(version);
273  widevine_cdm.version = version;
274  widevine_cdm.pk_hash.assign(kSha2Hash, &kSha2Hash[sizeof(kSha2Hash)]);
275  if (cus->RegisterComponent(widevine_cdm) != ComponentUpdateService::kOk) {
276    NOTREACHED() << "Widevine CDM component registration failed.";
277    return;
278  }
279}
280
281void StartWidevineCdmUpdateRegistration(ComponentUpdateService* cus) {
282  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
283  base::FilePath base_dir = GetWidevineCdmBaseDirectory();
284  if (!base::PathExists(base_dir) &&
285      !file_util::CreateDirectory(base_dir)) {
286    NOTREACHED() << "Could not create Widevine CDM directory.";
287    return;
288  }
289
290  base::FilePath latest_dir;
291  base::Version version(kNullVersion);
292  std::vector<base::FilePath> older_dirs;
293
294  if (GetWidevineCdmDirectory(&latest_dir, &version, &older_dirs)) {
295    base::FilePath adapter_path =
296        latest_dir.AppendASCII(kWidevineCdmAdapterFileName);
297    base::FilePath cdm_path = latest_dir.AppendASCII(kWidevineCdmFileName);
298
299    if (base::PathExists(adapter_path) &&
300        base::PathExists(cdm_path)) {
301      BrowserThread::PostTask(
302          BrowserThread::UI, FROM_HERE,
303          base::Bind(&RegisterWidevineCdmWithChrome, adapter_path, version));
304    } else {
305      base::DeleteFile(latest_dir, true);
306      version = base::Version(kNullVersion);
307    }
308  }
309
310  BrowserThread::PostTask(
311      BrowserThread::UI, FROM_HERE,
312      base::Bind(&FinishWidevineCdmUpdateRegistration, cus, version));
313
314  // Remove older versions of Widevine CDM.
315  for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
316       iter != older_dirs.end(); ++iter) {
317    base::DeleteFile(*iter, true);
318  }
319}
320
321#endif  // defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
322
323}  // namespace
324
325void RegisterWidevineCdmComponent(ComponentUpdateService* cus) {
326#if defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
327  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
328                          base::Bind(&StartWidevineCdmUpdateRegistration, cus));
329#endif  // defined(WIDEVINE_CDM_AVAILABLE) && defined(WIDEVINE_CDM_IS_COMPONENT)
330}
331