blob: 4785168750e46b723c93ee3a44f77047316b60cb [file] [log] [blame]
// Copyright 2018 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/browser/ash/fileapi/external_file_resolver.h"
#include <utility>
#include "base/functional/bind.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/fileapi/external_file_url_util.h"
#include "chrome/browser/ash/fileapi/file_system_backend.h"
#include "chrome/browser/browser_process.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/storage_partition.h"
#include "content/public/common/url_constants.h"
#include "extensions/browser/api/file_handlers/mime_util.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_util.h"
#include "storage/browser/file_system/file_stream_reader.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/isolated_context.h"
#include "url/gurl.h"
namespace ash {
namespace {
const char kMimeTypeForRFC822[] = "message/rfc822";
const char kMimeTypeForMHTML[] = "multipart/related";
// Helper for obtaining FileSystemContext, FileSystemURL, and mime type on the
// UI thread.
class URLHelper {
public:
// The scoped pointer to control lifetime of the instance itself. The pointer
// is passed to callback functions and binds the lifetime of the instance to
// the callback's lifetime.
using Lifetime = std::unique_ptr<URLHelper>;
using HelperCallback = base::OnceCallback<void(
net::Error,
scoped_refptr<storage::FileSystemContext> file_system_context,
file_manager::util::FileSystemURLAndHandle isolated_file_system,
const std::string& mime_type)>;
URLHelper(void* profile_id, const GURL& url, HelperCallback callback)
: callback_(std::move(callback)) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
Lifetime lifetime(this);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&URLHelper::RunOnUIThread, base::Unretained(this),
std::move(lifetime), profile_id, url));
}
URLHelper(const URLHelper&) = delete;
URLHelper& operator=(const URLHelper&) = delete;
private:
void RunOnUIThread(Lifetime lifetime, void* profile_id, const GURL& url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!g_browser_process->profile_manager()->IsValidProfile(profile_id)) {
ReplyResult(net::ERR_FAILED);
return;
}
Profile* const profile = reinterpret_cast<Profile*>(profile_id);
content::StoragePartition* const storage =
profile->GetDefaultStoragePartition();
DCHECK(storage);
scoped_refptr<storage::FileSystemContext> context =
storage->GetFileSystemContext();
DCHECK(context.get());
// Obtain the absolute path in the file system.
const base::FilePath virtual_path = ExternalFileURLToVirtualPath(url);
// Obtain the file system URL.
isolated_file_system_ =
file_manager::util::CreateIsolatedURLFromVirtualPath(
*context, url::Origin(), virtual_path);
// Check if the obtained path providing external file URL or not.
if (!isolated_file_system_.url.is_valid()) {
ReplyResult(net::ERR_INVALID_URL);
return;
}
if (!IsExternalFileURLType(isolated_file_system_.url.type())) {
ReplyResult(net::ERR_FAILED);
return;
}
file_system_context_ = std::move(context);
extensions::app_file_handler_util::GetMimeTypeForLocalPath(
profile, isolated_file_system_.url.path(),
base::BindOnce(&URLHelper::OnGotMimeTypeOnUIThread,
base::Unretained(this), std::move(lifetime)));
}
void OnGotMimeTypeOnUIThread(Lifetime lifetime,
const std::string& mime_type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
mime_type_ = mime_type;
if (mime_type_ == kMimeTypeForRFC822)
mime_type_ = kMimeTypeForMHTML;
ReplyResult(net::OK);
}
void ReplyResult(net::Error error) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback_), error,
std::move(file_system_context_),
std::move(isolated_file_system_), mime_type_));
}
HelperCallback callback_;
scoped_refptr<storage::FileSystemContext> file_system_context_;
file_manager::util::FileSystemURLAndHandle isolated_file_system_;
std::string mime_type_;
};
} // namespace
ExternalFileResolver::ExternalFileResolver(void* profile_id)
: profile_id_(profile_id), range_parse_result_(net::OK) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
}
ExternalFileResolver::~ExternalFileResolver() = default;
void ExternalFileResolver::ProcessHeaders(
const net::HttpRequestHeaders& headers) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// Read the range header if present.
std::optional<std::string> range_header =
headers.GetHeader(net::HttpRequestHeaders::kRange);
if (range_header) {
// Currently this job only cares about the Range header, and only supports
// single range requests.
std::vector<net::HttpByteRange> ranges;
if (net::HttpUtil::ParseRangeHeader(*range_header, &ranges) &&
ranges.size() == 1) {
byte_range_ = ranges[0];
} else {
range_parse_result_ = net::ERR_REQUEST_RANGE_NOT_SATISFIABLE;
}
}
}
void ExternalFileResolver::Resolve(const std::string& method,
const GURL& url,
ErrorCallback error_callback,
RedirectCallback redirect_callback,
StreamCallback stream_callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
error_callback_ = std::move(error_callback);
redirect_callback_ = std::move(redirect_callback);
stream_callback_ = std::move(stream_callback);
if (range_parse_result_ != net::OK) {
std::move(error_callback_).Run(range_parse_result_);
return;
}
// We only support GET request.
if (method != "GET") {
LOG(WARNING) << "Failed to start request: " << method
<< " method is not supported";
std::move(error_callback_).Run(net::ERR_METHOD_NOT_SUPPORTED);
return;
}
// Check if the scheme is correct.
if (!url.SchemeIs(content::kExternalFileScheme)) {
std::move(error_callback_).Run(net::ERR_INVALID_URL);
return;
}
// Owned by itself.
new URLHelper(profile_id_, url,
base::BindOnce(&ExternalFileResolver::OnHelperResultObtained,
weak_ptr_factory_.GetWeakPtr()));
}
void ExternalFileResolver::OnHelperResultObtained(
net::Error error,
scoped_refptr<storage::FileSystemContext> file_system_context,
file_manager::util::FileSystemURLAndHandle isolated_file_system,
const std::string& mime_type) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (error != net::OK) {
std::move(error_callback_).Run(error);
return;
}
DCHECK(file_system_context.get());
isolated_file_system_ = std::move(isolated_file_system);
mime_type_ = mime_type;
file_system_context_ = std::move(file_system_context);
file_system_context_->operation_runner()->GetMetadata(
isolated_file_system_.url,
{storage::FileSystemOperation::GetMetadataField::kIsDirectory,
storage::FileSystemOperation::GetMetadataField::kSize},
base::BindOnce(&ExternalFileResolver::OnFileInfoObtained,
weak_ptr_factory_.GetWeakPtr()));
}
void ExternalFileResolver::OnFileInfoObtained(
base::File::Error error,
const base::File::Info& file_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (error == base::File::FILE_ERROR_NOT_FOUND) {
std::move(error_callback_).Run(net::ERR_FILE_NOT_FOUND);
return;
}
if (error != base::File::FILE_OK || file_info.is_directory ||
file_info.size < 0) {
std::move(error_callback_).Run(net::ERR_FAILED);
return;
}
// Compute content size.
if (!byte_range_.ComputeBounds(file_info.size)) {
std::move(error_callback_).Run(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
return;
}
const int64_t offset = byte_range_.first_byte_position();
const int64_t remaining_bytes = byte_range_.last_byte_position() - offset + 1;
std::unique_ptr<storage::FileStreamReader> stream_reader =
file_system_context_->CreateFileStreamReader(
isolated_file_system_.url, offset, remaining_bytes, base::Time());
if (!stream_reader) {
std::move(error_callback_).Run(net::ERR_FILE_NOT_FOUND);
return;
}
std::move(stream_callback_)
.Run(mime_type_, std::move(isolated_file_system_.handle),
std::move(stream_reader), remaining_bytes);
}
} // namespace ash