910e62b5创建于 1月15日历史提交
// 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_view> range_header =
      headers.GetHeaderView(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