// 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 "build/branding_buildflags.h"
#include "chrome/common/chrome_paths.h"

namespace {

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

// Returns the hint file contents as a base::DictValue. Returned result may be
// an empty dictionary if the hint file does not exist or is formatted
// incorrectly.
base::DictValue GetHintFileContents() {
  base::FilePath hint_file_path;
  CHECK(base::PathService::Get(chrome::FILE_COMPONENT_WIDEVINE_CDM_HINT,
                               &hint_file_path));

  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::DictValue();
  }

  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::DictValue();
  }

  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::DictValue();
  }

  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());

  base::FilePath hint_file_path;
  CHECK(base::PathService::Get(chrome::FILE_COMPONENT_WIDEVINE_CDM_HINT,
                               &hint_file_path));

#if BUILDFLAG(CHROME_FOR_TESTING)
  // Chrome for Testing may store components in a directory other than the user
  // data directory. In this case, the WidevineCdm subdirectory will not be
  // created in user data directory during components registration, causing the
  // hint file write to fail.
  if (!base::CreateDirectory(hint_file_path.DirName())) {
    DLOG(ERROR) << "Could not create the directory for the CDM hint file: "
                << hint_file_path.DirName();
    return false;
  }
#endif

  base::DictValue 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);
}

base::FilePath GetHintedWidevineCdmDirectory() {
  base::DictValue 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::DictValue 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;
}
