blob: fa67789e09c0ee67a0a4a8d57f05dcbd755dfd1e [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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_storage_impl.h"
#include <map>
#include <memory>
#include <tuple>
#include <utility>
#include "base/callback.h"
#include "base/logging.h"
#include "content/browser/media/cdm_file_impl.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "ppapi/shared_impl/ppapi_constants.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_operation_context.h"
#include "storage/browser/fileapi/isolated_context.h"
#include "storage/common/fileapi/file_system_types.h"
#include "storage/common/fileapi/file_system_util.h"
#include "url/origin.h"
// Currently this uses the PluginPrivateFileSystem as the previous CDMs ran
// as pepper plugins and we need to be able to access any existing files.
// TODO(jrummell): Switch to using a separate file system once CDMs no
// longer run as pepper plugins.
namespace content {
// static
void CdmStorageImpl::Create(RenderFrameHost* render_frame_host,
const std::string& cdm_file_system_id,
media::mojom::CdmStorageRequest request) {
DVLOG(3) << __func__;
DCHECK(!render_frame_host->GetLastCommittedOrigin().opaque())
<< "Invalid origin specified for CdmStorageImpl::Create";
// Take a reference to the FileSystemContext.
scoped_refptr<storage::FileSystemContext> file_system_context;
StoragePartition* storage_partition =
render_frame_host->GetProcess()->GetStoragePartition();
if (storage_partition)
file_system_context = storage_partition->GetFileSystemContext();
// The created object is bound to (and owned by) |request|.
new CdmStorageImpl(render_frame_host, cdm_file_system_id,
std::move(file_system_context), std::move(request));
}
// static
bool CdmStorageImpl::IsValidCdmFileSystemId(
const std::string& cdm_file_system_id) {
// To be compatible with PepperFileSystemBrowserHost::GeneratePluginId(),
// |cdm_file_system_id| must contain only letters (A-Za-z), digits(0-9),
// or "._-".
for (const auto& ch : cdm_file_system_id) {
if (!base::IsAsciiAlpha(ch) && !base::IsAsciiDigit(ch) && ch != '.' &&
ch != '_' && ch != '-') {
return false;
}
}
// Also ensure that |cdm_file_system_id| contains at least 1 character.
return !cdm_file_system_id.empty();
}
CdmStorageImpl::CdmStorageImpl(
RenderFrameHost* render_frame_host,
const std::string& cdm_file_system_id,
scoped_refptr<storage::FileSystemContext> file_system_context,
media::mojom::CdmStorageRequest request)
: FrameServiceBase(render_frame_host, std::move(request)),
cdm_file_system_id_(cdm_file_system_id),
file_system_context_(std::move(file_system_context)),
child_process_id_(render_frame_host->GetProcess()->GetID()),
weak_factory_(this) {}
CdmStorageImpl::~CdmStorageImpl() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}
void CdmStorageImpl::Open(const std::string& file_name, OpenCallback callback) {
DVLOG(3) << __func__ << " file: " << file_name;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!IsValidCdmFileSystemId(cdm_file_system_id_)) {
DVLOG(1) << "CdmStorageImpl not initialized properly.";
std::move(callback).Run(Status::kFailure, base::File(), nullptr);
return;
}
if (file_name.empty()) {
DVLOG(1) << "No file specified.";
std::move(callback).Run(Status::kFailure, base::File(), nullptr);
return;
}
// The file system should only be opened once. If it has been attempted and
// failed, we can't create the CdmFile objects.
if (file_system_state_ == FileSystemState::kError) {
std::move(callback).Run(Status::kFailure, base::File(), nullptr);
return;
}
// If the file system is already open, create and initialize the CdmFileImpl
// object.
if (file_system_state_ == FileSystemState::kOpened) {
CreateCdmFile(file_name, std::move(callback));
return;
}
// Save a file name and callback for when the file system is open. If the
// open is already in progress, nothing more to do until the existing
// OpenPluginPrivateFileSystem() call completes.
pending_open_calls_.emplace(pending_open_calls_.end(), file_name,
std::move(callback));
if (file_system_state_ == FileSystemState::kOpening)
return;
DCHECK_EQ(FileSystemState::kUnopened, file_system_state_);
file_system_state_ = FileSystemState::kOpening;
std::string fsid =
storage::IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
storage::kFileSystemTypePluginPrivate, ppapi::kPluginPrivateRootName,
base::FilePath());
if (!storage::ValidateIsolatedFileSystemId(fsid)) {
DVLOG(1) << "Invalid file system ID.";
OnFileSystemOpened(base::File::FILE_ERROR_NOT_FOUND);
return;
}
// Grant full access of isolated file system to child process.
ChildProcessSecurityPolicy::GetInstance()->GrantCreateReadWriteFileSystem(
child_process_id_, fsid);
// Keep track of the URI for this instance of the PluginPrivateFileSystem.
file_system_root_uri_ = storage::GetIsolatedFileSystemRootURIString(
origin().GetURL(), fsid, ppapi::kPluginPrivateRootName);
file_system_context_->OpenPluginPrivateFileSystem(
origin().GetURL(), storage::kFileSystemTypePluginPrivate, fsid,
cdm_file_system_id_, storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
base::BindOnce(&CdmStorageImpl::OnFileSystemOpened,
weak_factory_.GetWeakPtr()));
}
void CdmStorageImpl::OnFileSystemOpened(base::File::Error error) {
DVLOG(3) << __func__;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_EQ(FileSystemState::kOpening, file_system_state_);
if (error != base::File::FILE_OK) {
file_system_state_ = FileSystemState::kError;
// All pending calls will fail.
for (auto& pending : pending_open_calls_) {
std::move(pending.second).Run(Status::kFailure, base::File(), nullptr);
}
pending_open_calls_.clear();
return;
}
// File system successfully opened, so create the CdmFileImpl object for
// all pending Open() calls.
file_system_state_ = FileSystemState::kOpened;
for (auto& pending : pending_open_calls_) {
CreateCdmFile(pending.first, std::move(pending.second));
}
pending_open_calls_.clear();
}
void CdmStorageImpl::CreateCdmFile(const std::string& file_name,
OpenCallback callback) {
DVLOG(3) << __func__ << " file: " << file_name;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_EQ(FileSystemState::kOpened, file_system_state_);
// File system opened successfully, so create an CdmFileImpl object and
// initialize it (which actually opens the file for reading).
auto cdm_file_impl = std::make_unique<CdmFileImpl>(
file_name, origin(), cdm_file_system_id_, file_system_root_uri_,
file_system_context_);
auto* cdm_file_ptr = cdm_file_impl.get();
cdm_file_ptr->Initialize(base::BindOnce(
&CdmStorageImpl::OnCdmFileInitialized, weak_factory_.GetWeakPtr(),
std::move(cdm_file_impl), std::move(callback)));
}
void CdmStorageImpl::OnCdmFileInitialized(
std::unique_ptr<CdmFileImpl> cdm_file_impl,
OpenCallback callback,
base::File file) {
DVLOG(3) << __func__;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!file.IsValid()) {
// Unable to open the file requested. Return an appropriate error.
Status status = (file.error_details() == base::File::FILE_ERROR_IN_USE)
? Status::kInUse
: Status::kFailure;
std::move(callback).Run(status, base::File(), nullptr);
return;
}
// File was opened successfully, so create the binding and return success.
media::mojom::CdmFileAssociatedPtrInfo cdm_file;
cdm_file_bindings_.AddBinding(std::move(cdm_file_impl),
mojo::MakeRequest(&cdm_file));
std::move(callback).Run(Status::kSuccess, std::move(file),
std::move(cdm_file));
}
} // namespace content