910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/media/cdm_file_impl.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_util.h"
#include "content/browser/media/cdm_storage_manager.h"
#include "media/cdm/cdm_helpers.h"
#include "media/cdm/cdm_type.h"
#include "media/mojo/mojom/cdm_storage.mojom.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "storage/browser/quota/quota_manager.h"
#include "storage/common/file_system/file_system_types.h"

namespace content {

namespace {

// The CDM interface has a restriction that file names can not begin with _,
// so use it to prefix temporary files.
const char kTemporaryFilePrefix = '_';

// Maximum length of a file name.
const size_t kFileNameMaxLength = 256;

// UMA suffices for CDM File IO operations.
const char kReadFile[] = "ReadFile";
const char kWriteFile[] = "WriteFile";
const char kDeleteFile[] = "DeleteFile";

}  // namespace

// static
bool CdmFileImpl::IsValidName(const std::string& name) {
  // File names must only contain letters (A-Za-z), digits(0-9), or "._-",
  // and not start with "_". It must contain at least 1 character, and not
  // more then |kFileNameMaxLength| characters.
  if (name.empty() || name.length() > kFileNameMaxLength ||
      name[0] == kTemporaryFilePrefix) {
    return false;
  }

  for (const auto ch : name) {
    if (!base::IsAsciiAlpha(ch) && !base::IsAsciiDigit(ch) && ch != '.' &&
        ch != '_' && ch != '-') {
      return false;
    }
  }

  return true;
}

CdmFileImpl::CdmFileImpl(
    CdmStorageManager* manager,
    const blink::StorageKey& storage_key,
    const media::CdmType& cdm_type,
    const std::string& file_name,
    mojo::PendingAssociatedReceiver<media::mojom::CdmFile> pending_receiver)
    : file_name_(file_name),
      cdm_type_(cdm_type),
      storage_key_(storage_key),
      cdm_storage_manager_(manager) {
  DVLOG(3) << __func__ << " " << file_name_;
  DCHECK(IsValidName(file_name_));
  DCHECK(cdm_storage_manager_);

  receiver_.Bind(std::move(pending_receiver));
  receiver_.set_disconnect_handler(base::BindOnce(
      &CdmFileImpl::OnReceiverDisconnect, weak_factory_.GetWeakPtr()));
}

CdmFileImpl::~CdmFileImpl() {
  DVLOG(3) << __func__ << " " << file_name_;
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);

  if (read_callback_)
    std::move(read_callback_).Run(Status::kFailure, {});

  if (write_callback_)
    std::move(write_callback_).Run(Status::kFailure);
}

void CdmFileImpl::Read(ReadCallback callback) {
  DVLOG(3) << __func__ << " file: " << file_name_;
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(cdm_storage_manager_);

  // Only 1 Read() or Write() is allowed at any time.
  if (read_callback_ || write_callback_) {
    std::move(callback).Run(Status::kFailure, {});
    return;
  }

  // Save |callback| for later use.
  read_callback_ = std::move(callback);
  start_time_ = base::TimeTicks::Now();

  cdm_storage_manager_->ReadFile(
      storage_key_, cdm_type_, file_name_,
      base::BindOnce(&CdmFileImpl::DidRead, weak_factory_.GetWeakPtr()));
}

void CdmFileImpl::DidRead(std::optional<std::vector<uint8_t>> data) {
  DVLOG(3) << __func__ << " file: " << file_name_
           << ", success: " << (data.has_value() ? "yes" : "no");
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(read_callback_);
  DCHECK(cdm_storage_manager_);

  bool success = data.has_value();
  ReportFileOperationUMA(success, kReadFile);

  if (!success) {
    // Unable to read the contents of the file.
    std::move(read_callback_).Run(Status::kFailure, {});
    return;
  }

  std::move(read_callback_).Run(Status::kSuccess, std::move(data.value()));
}

void CdmFileImpl::Write(const std::vector<uint8_t>& data,
                        WriteCallback callback) {
  DVLOG(3) << __func__ << " file: " << file_name_ << ", size: " << data.size();
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(cdm_storage_manager_);

  // Only 1 Read() or Write() is allowed at any time.
  if (read_callback_ || write_callback_) {
    std::move(callback).Run(Status::kFailure);
    return;
  }

  // Files are limited in size, so fail if file too big. This should have been
  // checked by the caller, but we don't fully trust IPC.
  if (data.size() > media::kMaxFileSizeBytes) {
    DLOG(WARNING) << __func__
                  << " Too much data to write. #bytes = " << data.size();
    std::move(callback).Run(Status::kFailure);
    return;
  }

  // Save |callback| for later use.
  write_callback_ = std::move(callback);
  start_time_ = base::TimeTicks::Now();

  // If there is no data to write, delete the file to save space.
  // |write_callback_| will be called after the file is deleted.
  if (data.empty()) {
    DeleteFile();
    return;
  }

  cdm_storage_manager_->WriteFile(
      storage_key_, cdm_type_, file_name_, data,
      base::BindOnce(&CdmFileImpl::DidWrite, weak_factory_.GetWeakPtr()));
}

void CdmFileImpl::ReportFileOperationUMA(bool success,
                                         const std::string& operation) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(cdm_storage_manager_);

  // Strings for UMA names.
  static const char kUmaPrefix[] = "Media.EME.CdmFileIO";
  static const char kTimeTo[] = "TimeTo";

  const std::string mode_suffix =
      cdm_storage_manager_->in_memory() ? "Incognito" : "Normal";

  // Records the result to the base histogram as well as splitting it out by
  // incognito or normal mode.
  auto result_uma_name = base::JoinString({kUmaPrefix, operation}, ".");
  base::UmaHistogramBoolean(result_uma_name, success);
  base::UmaHistogramBoolean(
      base::JoinString({result_uma_name, mode_suffix}, "."), success);

  // Records the time taken to the base histogram as well as splitting it out by
  // incognito or normal mode. Only reported for successful operation.
  if (success) {
    auto time_taken = base::TimeTicks::Now() - start_time_;
    auto time_taken_uma_name =
        base::JoinString({kUmaPrefix, kTimeTo, operation}, ".");
    base::UmaHistogramTimes(time_taken_uma_name, time_taken);
    base::UmaHistogramTimes(
        base::JoinString({time_taken_uma_name, mode_suffix}, "."), time_taken);
  }
}

void CdmFileImpl::DidWrite(bool success) {
  DVLOG(3) << __func__ << " file: " << file_name_;
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(write_callback_);
  DCHECK(cdm_storage_manager_);

  ReportFileOperationUMA(success, kWriteFile);

  if (!success) {
    DLOG(WARNING) << "Unable to write to file " << file_name_;
    std::move(write_callback_).Run(Status::kFailure);
    return;
  }

  std::move(write_callback_).Run(Status::kSuccess);
}

void CdmFileImpl::DeleteFile() {
  DVLOG(3) << __func__;
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(write_callback_);
  DCHECK(cdm_storage_manager_);

  DVLOG(3) << "Deleting " << file_name_;

  cdm_storage_manager_->DeleteFile(
      storage_key_, cdm_type_, file_name_,
      base::BindOnce(&CdmFileImpl::DidDeleteFile, weak_factory_.GetWeakPtr()));
}

void CdmFileImpl::DidDeleteFile(bool success) {
  DVLOG(3) << __func__ << " file: " << file_name_;
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(write_callback_);
  DCHECK(cdm_storage_manager_);

  ReportFileOperationUMA(success, kDeleteFile);

  if (!success) {
    DLOG(WARNING) << "Unable to delete file " << file_name_;
    std::move(write_callback_).Run(Status::kFailure);
    return;
  }

  std::move(write_callback_).Run(Status::kSuccess);
}

void CdmFileImpl::OnReceiverDisconnect() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(cdm_storage_manager_);

  // May delete `this`.
  cdm_storage_manager_->OnFileReceiverDisconnect(
      file_name_, cdm_type_, storage_key_, base::PassKey<CdmFileImpl>());
}

}  // namespace content