| // 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/bind.h" |
| #include "base/callback.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "content/browser/media/media_license_storage_host.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" |
| #include "third_party/blink/public/common/storage_key/storage_key.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 = '_'; |
| |
| // File size limit is 512KB. Licenses saved by the CDM are typically several |
| // hundreds of bytes. |
| const int64_t kMaxFileSizeBytes = 512 * 1024; |
| |
| // Maximum length of a file name. |
| const size_t kFileNameMaxLength = 256; |
| |
| const char kReadTimeUmaName[] = "Media.EME.CdmFileIO.TimeTo.ReadFile"; |
| const char kWriteTimeUmaName[] = "Media.EME.CdmFileIO.TimeTo.WriteFile"; |
| const char kDeleteTimeUmaName[] = "Media.EME.CdmFileIO.TimeTo.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( |
| MediaLicenseStorageHost* host, |
| 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), host_(host) { |
| DVLOG(3) << __func__ << " " << file_name_; |
| DCHECK(IsValidName(file_name_)); |
| DCHECK(host_); |
| |
| 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(host_); |
| |
| // 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(); |
| |
| host_->ReadFile( |
| cdm_type_, file_name_, |
| base::BindOnce(&CdmFileImpl::DidRead, weak_factory_.GetWeakPtr())); |
| } |
| |
| void CdmFileImpl::DidRead(absl::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(host_); |
| |
| if (!data.has_value()) { |
| // Unable to read the contents of the file. |
| std::move(read_callback_).Run(Status::kFailure, {}); |
| return; |
| } |
| |
| // Only report reading time for successful reads. |
| ReportFileOperationTimeUMA(kReadTimeUmaName); |
| 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(host_); |
| |
| // 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() > 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; |
| } |
| |
| host_->WriteFile( |
| cdm_type_, file_name_, data, |
| base::BindOnce(&CdmFileImpl::DidWrite, weak_factory_.GetWeakPtr())); |
| } |
| |
| void CdmFileImpl::ReportFileOperationTimeUMA(const std::string& uma_name) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(host_); |
| |
| static const char kIncognito[] = ".Incognito"; |
| static const char kNormal[] = ".Normal"; |
| |
| bool is_incognito = host_->in_memory(); |
| |
| // This records the time taken to the base histogram as well as splitting it |
| // out by incognito or normal mode. |
| auto time_taken = base::TimeTicks::Now() - start_time_; |
| base::UmaHistogramTimes(uma_name, time_taken); |
| base::UmaHistogramTimes( |
| base::StrCat({uma_name, is_incognito ? kIncognito : kNormal}), |
| time_taken); |
| } |
| |
| void CdmFileImpl::DidWrite(bool success) { |
| DVLOG(3) << __func__ << " file: " << file_name_; |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(write_callback_); |
| DCHECK(host_); |
| |
| if (!success) { |
| DLOG(WARNING) << "Unable to write to file " << file_name_; |
| std::move(write_callback_).Run(Status::kFailure); |
| return; |
| } |
| |
| // Only report writing time for successful writes. |
| ReportFileOperationTimeUMA(kWriteTimeUmaName); |
| std::move(write_callback_).Run(Status::kSuccess); |
| } |
| |
| void CdmFileImpl::DeleteFile() { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(write_callback_); |
| DCHECK(host_); |
| |
| DVLOG(3) << "Deleting " << file_name_; |
| |
| host_->DeleteFile( |
| 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(host_); |
| |
| if (!success) { |
| DLOG(WARNING) << "Unable to delete file " << file_name_; |
| std::move(write_callback_).Run(Status::kFailure); |
| return; |
| } |
| |
| // Only report writing time for successful deletions. |
| ReportFileOperationTimeUMA(kDeleteTimeUmaName); |
| std::move(write_callback_).Run(Status::kSuccess); |
| } |
| |
| void CdmFileImpl::OnReceiverDisconnect() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(host_); |
| |
| // May delete `this`. |
| host_->OnFileReceiverDisconnect(file_name_, cdm_type_, |
| base::PassKey<CdmFileImpl>()); |
| } |
| |
| } // namespace content |