chromium/ash/webui/demo_mode_app_ui/demo_mode_app_untrusted_ui.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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/webui/demo_mode_app_ui/demo_mode_app_untrusted_ui.h"

#include <memory>

#include "ash/display/screen_orientation_controller.h"
#include "ash/shell.h"
#include "ash/webui/common/chrome_os_webui_config.h"
#include "ash/webui/demo_mode_app_ui/demo_mode_untrusted_page_handler.h"
#include "ash/webui/demo_mode_app_ui/url_constants.h"
#include "ash/webui/grit/ash_demo_mode_app_resources.h"
#include "ash/webui/grit/ash_demo_mode_app_resources_map.h"
#include "base/containers/fixed_flat_set.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/task/thread_pool.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"
#include "chromeos/constants/chromeos_features.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/common/url_constants.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "ui/views/widget/widget.h"

namespace ash {

DemoModeAppUntrustedUIConfig::DemoModeAppUntrustedUIConfig(
    CreateWebUIControllerFunc create_controller_func)
    : ChromeOSWebUIConfig(content::kChromeUIUntrustedScheme,
                          kChromeUntrustedUIDemoModeAppHost,
                          create_controller_func) {}

DemoModeAppUntrustedUIConfig::~DemoModeAppUntrustedUIConfig() = default;

bool DemoModeAppUntrustedUIConfig::IsWebUIEnabled(
    content::BrowserContext* browser_context) {
  if (!InstallAttributes::IsInitialized()) {
    return false;
  }

  return InstallAttributes::Get()->IsDeviceInDemoMode();
}

scoped_refptr<base::RefCountedMemory> ReadFile(
    const base::FilePath& absolute_resource_path) {
  std::string data;
  base::ReadFileToString(absolute_resource_path, &data);
  return base::MakeRefCounted<base::RefCountedString>(std::move(data));
}

bool ShouldSourceFromComponent(
    const base::flat_set<std::string>& webui_resource_paths,
    const std::string& path) {
  // TODO(b/232945108): Consider changing this logic to check if the absolute
  // path exists in the component. This would still allow us show the default
  // WebUI resource if the requested path isn't found.
  return !webui_resource_paths.contains(path);
}

void DemoModeAppUntrustedUI::SourceDataFromComponent(
    const base::FilePath& component_path,
    const std::string& resource_path,
    content::WebUIDataSource::GotDataCallback callback) {
  std::string resource_path_or_root =
      resource_path == "" ? "index.html" : resource_path;
  // Convert to GURL to strip out query params and URL fragments
  //
  // TODO (b/234170189): Verify that query params won't be used in the prod Demo
  // App, or add support for them here instead of ignoring them.
  GURL full_url =
      GURL(kChromeUntrustedUIDemoModeAppURL + resource_path_or_root);
  // Trim leading slash from path
  std::string path = full_url.path().substr(1);

  base::FilePath absolute_resource_path = component_path.AppendASCII(path);

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock()},
      base::BindOnce(&ReadFile, absolute_resource_path), std::move(callback));
}

DemoModeAppUntrustedUI::DemoModeAppUntrustedUI(
    content::WebUI* web_ui,
    base::FilePath component_path,
    std::unique_ptr<DemoModeAppDelegate> delegate)
    : ui::UntrustedWebUIController(web_ui), delegate_(std::move(delegate)) {
  // We tack the resource path onto this component path, so CHECK that it's
  // absolute so ".." parent references can't be used as an exploit
  DCHECK(component_path.IsAbsolute());
  content::WebUIDataSource* data_source =
      content::WebUIDataSource::CreateAndAdd(
          web_ui->GetWebContents()->GetBrowserContext(),
          kChromeUntrustedUIDemoModeAppURL);

  base::flat_set<std::string> webui_resource_paths;
  // Add required resources.
  for (size_t i = 0; i < kAshDemoModeAppResourcesSize; ++i) {
    data_source->AddResourcePath(kAshDemoModeAppResources[i].path,
                                 kAshDemoModeAppResources[i].id);
    webui_resource_paths.insert(kAshDemoModeAppResources[i].path);
  }

  data_source->SetRequestFilter(
      base::BindRepeating(&ShouldSourceFromComponent, webui_resource_paths),
      base::BindRepeating(&SourceDataFromComponent, component_path));
  data_source->OverrideContentSecurityPolicy(
      network::mojom::CSPDirectiveName::StyleSrc,
      "style-src 'self' 'unsafe-inline';");
  data_source->OverrideContentSecurityPolicy(
      network::mojom::CSPDirectiveName::TrustedTypes,
      "trusted-types lit-html;");
}

DemoModeAppUntrustedUI::~DemoModeAppUntrustedUI() = default;

void DemoModeAppUntrustedUI::BindInterface(
    mojo::PendingReceiver<mojom::demo_mode::UntrustedPageHandlerFactory>
        factory) {
  if (demo_mode_page_factory_.is_bound()) {
    demo_mode_page_factory_.reset();
  }
  demo_mode_page_factory_.Bind(std::move(factory));
}

void DemoModeAppUntrustedUI::CreatePageHandler(
    mojo::PendingReceiver<mojom::demo_mode::UntrustedPageHandler> handler) {
  auto top_level_native_window =
      web_ui()->GetWebContents()->GetTopLevelNativeWindow();
  views::Widget* widget =
      views::Widget::GetWidgetForNativeWindow(top_level_native_window);
  demo_mode_page_handler_ = std::make_unique<DemoModeUntrustedPageHandler>(
      std::move(handler), widget, this);

  if (ash::features::IsDemoModeAppLandscapeLockedEnabled()) {
    // kLandscapePrimary means 0 degree. If it's kLandscapeSecondary, that means
    // 180 degrees upside down. When the demo mode app is closed,
    // UnlockOrientationForWindow() will be called before the window is
    // destroyed. The lock_info_map_ will not keep the demo mode app window
    // info.
    ash::Shell::Get()
        ->screen_orientation_controller()
        ->LockOrientationForWindow(
            top_level_native_window,
            chromeos::OrientationType::kLandscapePrimary);
  }
}

WEB_UI_CONTROLLER_TYPE_IMPL(DemoModeAppUntrustedUI)

}  // namespace ash