| // 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_resolver.h" |
| |
| #include "base/bind.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_url_util.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/fileapi/file_stream_reader.h" |
| #include "storage/browser/fileapi/file_system_context.h" |
| #include "storage/browser/fileapi/isolated_context.h" |
| #include "url/gurl.h" |
| |
| namespace chromeos { |
| 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, |
| const scoped_refptr<storage::FileSystemContext>& file_system_context, |
| std::unique_ptr<IsolatedFileSystemScope> isolated_file_system_scope, |
| const storage::FileSystemURL& file_system_url, |
| 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); |
| base::PostTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::UI}, |
| base::BindOnce(&URLHelper::RunOnUIThread, base::Unretained(this), |
| base::Passed(&lifetime), profile_id, url)); |
| } |
| |
| 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 = |
| content::BrowserContext::GetStoragePartitionForSite(profile, url); |
| 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. |
| file_system_url_ = file_manager::util::CreateIsolatedURLFromVirtualPath( |
| *context, /* empty origin */ GURL(), virtual_path); |
| |
| // Check if the obtained path providing external file URL or not. |
| if (!file_system_url_.is_valid()) { |
| ReplyResult(net::ERR_INVALID_URL); |
| return; |
| } |
| |
| isolated_file_system_scope_.reset( |
| new IsolatedFileSystemScope(file_system_url_.filesystem_id())); |
| |
| if (!IsExternalFileURLType(file_system_url_.type())) { |
| ReplyResult(net::ERR_FAILED); |
| return; |
| } |
| |
| file_system_context_ = context; |
| extensions::app_file_handler_util::GetMimeTypeForLocalPath( |
| profile, file_system_url_.path(), |
| base::BindRepeating(&URLHelper::OnGotMimeTypeOnUIThread, |
| base::Unretained(this), base::Passed(&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); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {content::BrowserThread::IO}, |
| base::BindOnce(std::move(callback_), error, file_system_context_, |
| base::Passed(&isolated_file_system_scope_), |
| file_system_url_, mime_type_)); |
| } |
| |
| HelperCallback callback_; |
| scoped_refptr<storage::FileSystemContext> file_system_context_; |
| std::unique_ptr<IsolatedFileSystemScope> isolated_file_system_scope_; |
| storage::FileSystemURL file_system_url_; |
| std::string mime_type_; |
| |
| DISALLOW_COPY_AND_ASSIGN(URLHelper); |
| }; |
| |
| } // namespace |
| |
| IsolatedFileSystemScope::IsolatedFileSystemScope( |
| const std::string& file_system_id) |
| : file_system_id_(file_system_id) {} |
| |
| IsolatedFileSystemScope::~IsolatedFileSystemScope() { |
| storage::IsolatedContext::GetInstance()->RevokeFileSystem(file_system_id_); |
| } |
| |
| ExternalFileResolver::ExternalFileResolver(void* profile_id) |
| : profile_id_(profile_id), |
| range_parse_result_(net::OK), |
| weak_ptr_factory_(this) { |
| 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::string range_header; |
| if (headers.GetHeader(net::HttpRequestHeaders::kRange, &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, |
| const scoped_refptr<storage::FileSystemContext>& file_system_context, |
| std::unique_ptr<IsolatedFileSystemScope> isolated_file_system_scope, |
| const storage::FileSystemURL& file_system_url, |
| 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()); |
| file_system_context_ = file_system_context; |
| isolated_file_system_scope_ = std::move(isolated_file_system_scope); |
| file_system_url_ = file_system_url; |
| mime_type_ = mime_type; |
| |
| // Check if the entry has a redirect URL. |
| file_system_context_->external_backend()->GetRedirectURLForContents( |
| file_system_url_, |
| base::BindRepeating(&ExternalFileResolver::OnRedirectURLObtained, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ExternalFileResolver::OnRedirectURLObtained(const GURL& redirect_url) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| if (!redirect_url.is_empty()) { |
| std::move(redirect_callback_).Run(mime_type_, redirect_url); |
| return; |
| } |
| |
| // If there's no redirect then we're serving the file from the file system. |
| file_system_context_->operation_runner()->GetMetadata( |
| file_system_url_, |
| storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY | |
| storage::FileSystemOperation::GET_METADATA_FIELD_SIZE, |
| 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( |
| 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_scope_), |
| std::move(stream_reader), remaining_bytes); |
| } |
| |
| } // namespace chromeos |