chromium/chromecast/cast_core/runtime/browser/grpc_resource_data_source.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromecast/cast_core/runtime/browser/grpc_resource_data_source.h"

#include <string_view>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/string_util.h"
#include "base/task/bind_post_task.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chromecast/base/cast_constants.h"
#include "net/base/mime_util.h"

namespace chromecast {

namespace {

// File extension types
constexpr const char kExtensionTypeCss[] = ".css";
constexpr const char kExtensionTypeJs[] = ".js";
constexpr const char kExtensionTypeJson[] = ".json";
constexpr const char kExtensionTypePdf[] = ".pdf";
constexpr const char kExtensionTypeSvg[] = ".svg";
constexpr const char kExtensionTypePng[] = ".png";
constexpr const char kExtensionTypeJpeg[] = ".jpeg";
constexpr const char kExtensionTypeHtml[] = ".html";

// Mime types of the resource requested by the Cast app's WebUI.
constexpr const char kMimeTypeHtml[] = "text/html";
constexpr const char kMimeTypeCss[] = "text/css";
constexpr const char kMimeTypeJavascript[] = "application/javascript";
constexpr const char kMimeTypeJson[] = "application/json";
constexpr const char kMimeTypePdf[] = "application/pdf";
constexpr const char kMimeTypeSvgXml[] = "image/svg+xml";
constexpr const char kMimeTypeJpeg[] = "image/jpeg";
constexpr const char kMimeTypePng[] = "image/png";
constexpr const char kAllowedOriginPrefix[] = "chrome://";

}  // namespace

GrpcResourceDataSource::GrpcResourceDataSource(
    const std::string host,
    bool for_webui,
    cast::v2::CoreApplicationServiceStub* core_app_service_stub)
    : task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
      host_(host),
      for_webui_(for_webui),
      core_app_service_stub_(core_app_service_stub) {
  DCHECK(!host.empty());
}

GrpcResourceDataSource::~GrpcResourceDataSource() = default;

std::string GrpcResourceDataSource::GetSource() {
  return host_;
}

void GrpcResourceDataSource::StartDataRequest(
    const GURL& url,
    const content::WebContents::Getter& wc_getter,
    content::URLDataSource::GotDataCallback callback) {
  DVLOG(1) << "Starting Data request for " << url;

  auto call = core_app_service_stub_->CreateCall<
      cast::v2::CoreApplicationServiceStub::GetWebUIResource>();
  call.request().set_resource_id(content::URLDataSource::URLToRequestPath(url));
  std::move(call).InvokeAsync(base::BindPostTask(
      task_runner_,
      base::BindOnce(&GrpcResourceDataSource::OnWebUiResourceReceived,
                     weak_factory_.GetWeakPtr(), std::move(callback))));
}

void GrpcResourceDataSource::OnWebUiResourceReceived(
    content::URLDataSource::GotDataCallback callback,
    cast::utils::GrpcStatusOr<cast::v2::GetWebUIResourceResponse> response_or) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  if (!response_or.ok()) {
    LOG(ERROR) << "Failed to receive resource path response: status="
               << response_or.ToString();
    std::move(callback).Run(nullptr);
    return;
  }

  DVLOG(1) << "Got resource path: " << response_or->resource_path();
  ReadResourceFile(response_or->resource_path(), std::move(callback));
}

void GrpcResourceDataSource::ReadResourceFile(
    std::string_view resource_file_path,
    content::URLDataSource::GotDataCallback callback) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  base::FilePath path(resource_file_path);
  if (!base::PathExists(path)) {
    LOG(ERROR) << "Resource " << resource_file_path << " does not exist";
    std::move(callback).Run(nullptr);
    return;
  }

  std::string text;
  base::ReadFileToString(base::FilePath(resource_file_path), &text);
  std::move(callback).Run(
      base::MakeRefCounted<base::RefCountedString>(std::move(text)));
}

// The Path can either be a filename or a remote url string starting with "?".
// Examples - "?remote_url=https://google.com", "fonts.css".
std::string GrpcResourceDataSource::GetMimeType(const GURL& url) {
  const std::string path = content::URLDataSource::URLToRequestPath(url);

  if (!for_webui_) {
    std::string mime_type;
    base::FilePath::StringType file_ext =
        base::FilePath().AppendASCII(path).Extension();
    if (!file_ext.empty())
      net::GetWellKnownMimeTypeFromExtension(file_ext.substr(1), &mime_type);
    return mime_type;
  }

  // If the path starts with ? or if the path does not contain an extension,
  // return default MimeType.
  auto extension_index = path.find_last_of(".");
  if (path.find("?") != std::string::npos ||
      extension_index == std::string::npos) {
    return kMimeTypeHtml;
  }

  base::FilePath file_path(path);
  auto extension = file_path.Extension();
  if (extension.empty()) {
    return kMimeTypeHtml;
  }
  if (base::EqualsCaseInsensitiveASCII(extension, kExtensionTypeCss)) {
    return kMimeTypeCss;
  }
  if (base::EqualsCaseInsensitiveASCII(extension, kExtensionTypeJs)) {
    return kMimeTypeJavascript;
  }
  if (base::EqualsCaseInsensitiveASCII(extension, kExtensionTypeJson)) {
    return kMimeTypeJson;
  }
  if (base::EqualsCaseInsensitiveASCII(extension, kExtensionTypePdf)) {
    return kMimeTypePdf;
  }
  if (base::EqualsCaseInsensitiveASCII(extension, kExtensionTypeSvg)) {
    return kMimeTypeSvgXml;
  }
  if (base::EqualsCaseInsensitiveASCII(extension, kExtensionTypeJpeg)) {
    return kMimeTypeJpeg;
  }
  if (base::EqualsCaseInsensitiveASCII(extension, kExtensionTypePng)) {
    return kMimeTypePng;
  }
  if (base::EqualsCaseInsensitiveASCII(extension, kExtensionTypeHtml)) {
    return kMimeTypeHtml;
  }

  NOTREACHED_IN_MIGRATION() << "Unknown Mime type of file " << path;
  return std::string();
}

bool GrpcResourceDataSource::ShouldServiceRequest(
    const GURL& url,
    content::BrowserContext* browser_context,
    int render_process_id) {
  if (url.SchemeIs(kChromeResourceScheme)) {
    return true;
  }
  return URLDataSource::ShouldServiceRequest(url, browser_context,
                                             render_process_id);
}

std::string GrpcResourceDataSource::GetAccessControlAllowOriginForOrigin(
    const std::string& origin) {
  if (!base::StartsWith(origin, kAllowedOriginPrefix,
                        base::CompareCase::SENSITIVE)) {
    return "";
  }
  return origin;
}

void GrpcResourceDataSource::OverrideContentSecurityPolicyChildSrc(
    const std::string& data) {
  frame_src_ = data;
}

void GrpcResourceDataSource::DisableDenyXFrameOptions() {
  deny_xframe_options_ = false;
}

bool GrpcResourceDataSource::ShouldDenyXFrameOptions() {
  return deny_xframe_options_;
}

}  // namespace chromecast