blob: 5ecaf341694cf78284f5b016070127399b7c7c67 [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/chromeos/fileapi/external_file_url_loader_factory.h"
#include <algorithm>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/task/post_task.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
#include "chrome/browser/chromeos/fileapi/external_file_resolver.h"
#include "chrome/browser/chromeos/fileapi/external_file_url_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/api/file_handlers/mime_util.h"
#include "mojo/public/c/system/types.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "net/base/io_buffer.h"
#include "net/http/http_byte_range.h"
#include "net/http/http_util.h"
#include "storage/browser/fileapi/file_stream_reader.h"
#include "storage/browser/fileapi/file_system_backend.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_operation_runner.h"
#include "storage/browser/fileapi/file_system_url.h"
#include "storage/browser/fileapi/isolated_context.h"
namespace chromeos {
namespace {
constexpr size_t kDefaultPipeSize = 65536;
// An IOBuffer that doesn't own its data.
class MojoPipeIOBuffer : public net::IOBuffer {
public:
explicit MojoPipeIOBuffer(void* data)
: net::IOBuffer(static_cast<char*>(data)) {}
protected:
~MojoPipeIOBuffer() override {
// Set data_ to null so ~IOBuffer won't try to delete it.
data_ = nullptr;
}
private:
DISALLOW_COPY_AND_ASSIGN(MojoPipeIOBuffer);
};
// A helper class to read data from a FileStreamReader, and write it to a
// Mojo data pipe.
class FileSystemReaderDataPipeProducer {
public:
FileSystemReaderDataPipeProducer(
mojo::ScopedDataPipeProducerHandle producer_handle,
std::unique_ptr<storage::FileStreamReader> stream_reader,
int remaining_bytes,
base::OnceCallback<void(net::Error)> callback)
: producer_handle_(std::move(producer_handle)),
stream_reader_(std::move(stream_reader)),
remaining_bytes_(remaining_bytes),
total_bytes_written_(0),
pipe_watcher_(std::make_unique<mojo::SimpleWatcher>(
FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
base::SequencedTaskRunnerHandle::Get())),
callback_(std::move(callback)),
weak_ptr_factory_(this) {
pipe_watcher_->Watch(
producer_handle_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
MOJO_WATCH_CONDITION_SATISFIED,
base::BindRepeating(&FileSystemReaderDataPipeProducer::OnHandleReady,
weak_ptr_factory_.GetWeakPtr()));
}
void Write() {
while (remaining_bytes_ > 0) {
if (!producer_handle_.is_valid())
CompleteWithResult(net::ERR_FAILED);
void* pipe_buffer;
uint32_t buffer_size = kDefaultPipeSize;
MojoResult result = producer_handle_->BeginWriteData(
&pipe_buffer, &buffer_size, MOJO_WRITE_DATA_FLAG_NONE);
// If we can't synchronously get the buffer to write to, stop for now and
// wait for the SimpleWatcher to notify us that the pipe is writable.
if (result == MOJO_RESULT_SHOULD_WAIT) {
pipe_watcher_->ArmOrNotify();
return;
}
if (result != MOJO_RESULT_OK) {
CompleteWithResult(MojoResultToErrorCode(result));
return;
}
DCHECK(base::IsValueInRangeForNumericType<int>(buffer_size));
scoped_refptr<MojoPipeIOBuffer> io_buffer =
base::MakeRefCounted<MojoPipeIOBuffer>(pipe_buffer);
const int read_size = stream_reader_->Read(
io_buffer.get(), std::min<int64_t>(buffer_size, remaining_bytes_),
base::BindOnce(
&FileSystemReaderDataPipeProducer::OnPendingReadComplete,
weak_ptr_factory_.GetWeakPtr()));
// Read will return ERR_IO_PENDING if the read couldn't be completed
// synchronously. In that case return, and OnPendingReadComplete will
// be called when the read is complete.
if (read_size == net::ERR_IO_PENDING)
return;
net::Error write_error = FinishWrite(read_size);
if (write_error != net::OK) {
CompleteWithResult(write_error);
return;
}
}
CompleteWithResult(net::OK);
}
int64_t total_bytes_written() { return total_bytes_written_; }
private:
net::Error FinishWrite(int read_size) {
MojoResult result =
producer_handle_->EndWriteData(std::max<int>(0, read_size));
if (read_size <= 0)
return static_cast<net::Error>(read_size);
if (result != MOJO_RESULT_OK)
return MojoResultToErrorCode(result);
remaining_bytes_ -= read_size;
total_bytes_written_ += read_size;
return net::OK;
}
void OnPendingReadComplete(int read_result) {
net::Error result = FinishWrite(read_result);
if (result != net::OK) {
CompleteWithResult(result);
return;
}
Write();
}
void OnHandleReady(MojoResult result, const mojo::HandleSignalsState& state) {
if (result != MOJO_RESULT_OK) {
CompleteWithResult(MojoResultToErrorCode(result));
return;
}
Write();
}
void CompleteWithResult(net::Error error) {
pipe_watcher_.reset();
std::move(callback_).Run(error);
}
net::Error MojoResultToErrorCode(MojoResult result) {
switch (result) {
case MOJO_RESULT_OK:
return net::OK;
case MOJO_RESULT_CANCELLED:
return net::ERR_ABORTED;
case MOJO_RESULT_DEADLINE_EXCEEDED:
return net::ERR_TIMED_OUT;
case MOJO_RESULT_NOT_FOUND:
return net::ERR_FILE_NOT_FOUND;
case MOJO_RESULT_PERMISSION_DENIED:
return net::ERR_ACCESS_DENIED;
case MOJO_RESULT_RESOURCE_EXHAUSTED:
return net::ERR_INSUFFICIENT_RESOURCES;
case MOJO_RESULT_UNIMPLEMENTED:
return net::ERR_NOT_IMPLEMENTED;
default:
return net::ERR_FAILED;
}
}
mojo::ScopedDataPipeProducerHandle producer_handle_;
std::unique_ptr<storage::FileStreamReader> stream_reader_;
int64_t remaining_bytes_;
int64_t total_bytes_written_;
std::unique_ptr<mojo::SimpleWatcher> pipe_watcher_;
base::OnceCallback<void(net::Error)> callback_;
base::WeakPtrFactory<FileSystemReaderDataPipeProducer> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(FileSystemReaderDataPipeProducer);
};
class ExternalFileURLLoader : public network::mojom::URLLoader {
public:
static void CreateAndStart(
void* profile_id,
const network::ResourceRequest& request,
network::mojom::URLLoaderRequest loader,
network::mojom::URLLoaderClientPtrInfo client_info) {
// Owns itself. Will live as long as its URLLoader and URLLoaderClientPtr
// bindings are alive - essentially until either the client gives up or all
// file data has been sent to it.
auto* external_file_url_loader = new ExternalFileURLLoader(
profile_id, std::move(loader), std::move(client_info));
external_file_url_loader->Start(request);
}
// network::mojom::URLLoader:
void FollowRedirect(const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const base::Optional<GURL>& new_url) override {}
void ProceedWithResponse() override {}
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override {}
void PauseReadingBodyFromNet() override {}
void ResumeReadingBodyFromNet() override {}
private:
explicit ExternalFileURLLoader(
void* profile_id,
network::mojom::URLLoaderRequest loader,
network::mojom::URLLoaderClientPtrInfo client_info)
: binding_(this),
resolver_(std::make_unique<ExternalFileResolver>(profile_id)),
weak_ptr_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
binding_.Bind(std::move(loader));
binding_.set_connection_error_handler(base::BindOnce(
&ExternalFileURLLoader::OnConnectionError, base::Unretained(this)));
client_.Bind(std::move(client_info));
}
~ExternalFileURLLoader() override = default;
void Start(const network::ResourceRequest& request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
head_.request_start = base::TimeTicks::Now();
resolver_->ProcessHeaders(request.headers);
resolver_->Resolve(
request.method, request.url,
base::BindOnce(&ExternalFileURLLoader::CompleteWithError,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&ExternalFileURLLoader::OnRedirectURLObtained,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&ExternalFileURLLoader::OnStreamObtained,
weak_ptr_factory_.GetWeakPtr()));
}
void OnRedirectURLObtained(const std::string& mime_type,
const GURL& redirect_url) {
head_.mime_type = mime_type;
head_.response_start = base::TimeTicks::Now();
head_.encoded_data_length = 0;
net::RedirectInfo redirect_info;
redirect_info.new_method = "GET";
redirect_info.status_code = 302;
redirect_info.new_url = redirect_url;
client_->OnReceiveRedirect(redirect_info, head_);
client_.reset();
MaybeDeleteSelf();
}
void OnStreamObtained(
const std::string& mime_type,
std::unique_ptr<IsolatedFileSystemScope> isolated_file_system_scope,
std::unique_ptr<storage::FileStreamReader> stream_reader,
int64_t size) {
head_.mime_type = mime_type;
head_.content_length = size;
isolated_file_system_scope_ = std::move(isolated_file_system_scope);
mojo::DataPipe pipe(kDefaultPipeSize);
if (!pipe.consumer_handle.is_valid() || !pipe.producer_handle.is_valid()) {
CompleteWithError(net::ERR_FAILED);
return;
}
head_.response_start = base::TimeTicks::Now();
client_->OnReceiveResponse(head_);
client_->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
data_producer_ = std::make_unique<FileSystemReaderDataPipeProducer>(
std::move(pipe.producer_handle), std::move(stream_reader), size,
base::BindOnce(&ExternalFileURLLoader::OnFileWritten,
weak_ptr_factory_.GetWeakPtr()));
data_producer_->Write();
}
void OnFileWritten(net::Error error) {
int64_t total_bytes_written = data_producer_->total_bytes_written();
data_producer_.reset();
if (error != net::OK) {
CompleteWithError(error);
return;
}
network::URLLoaderCompletionStatus status(net::OK);
status.encoded_data_length = total_bytes_written;
status.encoded_body_length = total_bytes_written;
status.decoded_body_length = total_bytes_written;
client_->OnComplete(status);
client_.reset();
MaybeDeleteSelf();
}
void CompleteWithError(net::Error net_error) {
client_->OnComplete(network::URLLoaderCompletionStatus(net_error));
client_.reset();
MaybeDeleteSelf();
}
void OnConnectionError() {
data_producer_.reset();
client_.reset();
binding_.Close();
MaybeDeleteSelf();
}
void MaybeDeleteSelf() {
if (!binding_.is_bound() && !client_.is_bound())
delete this;
}
mojo::Binding<network::mojom::URLLoader> binding_;
network::mojom::URLLoaderClientPtr client_;
std::unique_ptr<ExternalFileResolver> resolver_;
network::ResourceResponseHead head_;
std::unique_ptr<IsolatedFileSystemScope> isolated_file_system_scope_;
std::unique_ptr<FileSystemReaderDataPipeProducer> data_producer_;
base::WeakPtrFactory<ExternalFileURLLoader> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ExternalFileURLLoader);
};
} // namespace
ExternalFileURLLoaderFactory::ExternalFileURLLoaderFactory(
void* profile_id,
int render_process_host_id)
: profile_id_(profile_id),
render_process_host_id_(render_process_host_id) {}
ExternalFileURLLoaderFactory::~ExternalFileURLLoaderFactory() = default;
void ExternalFileURLLoaderFactory::CreateLoaderAndStart(
network::mojom::URLLoaderRequest loader,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
if (render_process_host_id_ != content::ChildProcessHost::kInvalidUniqueID &&
!content::ChildProcessSecurityPolicy::GetInstance()->CanRequestURL(
render_process_host_id_, request.url)) {
DVLOG(1) << "Denied unauthorized request for "
<< request.url.possibly_invalid_spec();
mojo::ReportBadMessage("Unauthorized externalfile request");
return;
}
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&ExternalFileURLLoader::CreateAndStart, profile_id_,
request, std::move(loader), client.PassInterface()));
}
void ExternalFileURLLoaderFactory::Clone(
network::mojom::URLLoaderFactoryRequest loader) {
bindings_.AddBinding(this, std::move(loader));
}
} // namespace chromeos