chromium/chrome/common/media/component_widevine_cdm_hint_file_linux.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/common/media/component_widevine_cdm_hint_file_linux.h"

#include <memory>
#include <string>
#include <utility>

#include "base/check.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/values.h"
#include "base/version.h"
#include "chrome/common/chrome_paths.h"

#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/notreached.h"
#include "media/base/media_switches.h"
#endif

namespace {

// Fields used inside the hint file.
const char kPath[] = "Path";
const char kLastBundledVersion[] = "LastBundledVersion";

// Returns the hint file contents as a Value::Dict. Returned result may be an
// empty dictionary if the hint file does not exist or is formatted incorrectly.
base::Value::Dict GetHintFileContents() {
  base::FilePath hint_file_path;
#if BUILDFLAG(IS_CHROMEOS_LACROS)
  // ChromeOS Lacros should use the Widevine CDM Component Updated by ChromeOS
  // Ash. This is determined by using command line arguments passed when Ash
  // launches Lacros.
  if (!base::FeatureList::IsEnabled(media::kLacrosUseAshWidevine)) {
    DVLOG(1) << "CDM hint file disabled.";
    return base::Value::Dict();
  }

  const auto* command_line = base::CommandLine::ForCurrentProcess();
  if (!command_line->HasSwitch(
          switches::kCrosWidevineComponentUpdatedHintFile)) {
    DVLOG(1) << "Command line switch " +
                    std::string(
                        switches::kCrosWidevineComponentUpdatedHintFile) +
                    " not found.";
    return base::Value::Dict();
  }

  hint_file_path = command_line->GetSwitchValuePath(
      switches::kCrosWidevineComponentUpdatedHintFile);
#else
  CHECK(base::PathService::Get(chrome::FILE_COMPONENT_WIDEVINE_CDM_HINT,
                               &hint_file_path));
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

  DVLOG(1) << __func__ << " checking " << hint_file_path;
  if (!base::PathExists(hint_file_path)) {
    DVLOG(1) << "CDM hint file at " << hint_file_path << " does not exist.";
    return base::Value::Dict();
  }

  std::string json_string;
  if (!base::ReadFileToString(hint_file_path, &json_string)) {
    DLOG(ERROR) << "Could not read the CDM hint file at " << hint_file_path;
    return base::Value::Dict();
  }

  std::string error_message;
  JSONStringValueDeserializer deserializer(json_string);
  std::unique_ptr<base::Value> dict =
      deserializer.Deserialize(/*error_code=*/nullptr, &error_message);

  if (!dict || !dict->is_dict()) {
    DLOG(ERROR) << "Could not deserialize the CDM hint file. Error: "
                << error_message;
    return base::Value::Dict();
  }

  return std::move(*dict).TakeDict();
}

}  // namespace

bool UpdateWidevineCdmHintFile(const base::FilePath& cdm_base_path,
                               std::optional<base::Version> bundled_version) {
  DCHECK(!cdm_base_path.empty());

#if BUILDFLAG(IS_CHROMEOS_LACROS)
  NOTREACHED() << "Lacros should not be updating the hint file.";
#else
  base::FilePath hint_file_path;
  CHECK(base::PathService::Get(chrome::FILE_COMPONENT_WIDEVINE_CDM_HINT,
                               &hint_file_path));

  base::Value::Dict dict;
  dict.Set(kPath, cdm_base_path.value());
  if (bundled_version.has_value()) {
    dict.Set(kLastBundledVersion, bundled_version.value().GetString());
  }

  std::string json_string;
  JSONStringValueSerializer serializer(&json_string);
  if (!serializer.Serialize(dict)) {
    DLOG(ERROR) << "Could not serialize the CDM hint file.";
    return false;
  }

  DVLOG(1) << __func__ << " setting " << cdm_base_path << " to " << json_string;
  return base::ImportantFileWriter::WriteFileAtomically(hint_file_path,
                                                        json_string);
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
}

base::FilePath GetHintedWidevineCdmDirectory() {
  base::Value::Dict dict = GetHintFileContents();

  auto* path_str = dict.FindString(kPath);
  if (!path_str) {
    DVLOG(1) << "CDM hint file missing " << kPath;
    return base::FilePath();
  }

  const base::FilePath path(*path_str);
  DLOG_IF(ERROR, path.empty())
      << "CDM hint file path " << *path_str << " is invalid.";
  DVLOG(1) << __func__ << " returns " << path;
  return path;
}

std::optional<base::Version> GetBundledVersionDuringLastComponentUpdate() {
  base::Value::Dict dict = GetHintFileContents();

  auto* version_str = dict.FindString(kLastBundledVersion);
  if (!version_str) {
    DVLOG(1) << "CDM hint file missing " << kLastBundledVersion;
    return std::nullopt;
  }

  const base::Version version(*version_str);
  DLOG_IF(ERROR, !version.IsValid())
      << "CDM hint file version " << *version_str << " is invalid.";
  DVLOG(1) << __func__ << " returns " << version;
  return version;
}