blob: 48737027ffbcc42279008a8c046591f8c3d70309 [file] [log] [blame]
// Copyright 2019 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/ash/web_applications/terminal_source.h"
#include "ash/constants/ash_features.h"
#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/crostini/crostini_terminal.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/webui_url_constants.h"
#include "components/prefs/pref_service.h"
#include "net/base/escape.h"
#include "net/base/mime_util.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "third_party/zlib/google/compression_utils.h"
#include "ui/webui/webui_allowlist.h"
namespace {
constexpr base::FilePath::CharType kTerminalRoot[] =
FILE_PATH_LITERAL("/usr/share/chromeos-assets/crosh_builtin");
constexpr char kDefaultMime[] = "text/html";
void ReadFile(const std::string& relative_path,
content::URLDataSource::GotDataCallback callback) {
std::string content;
base::FilePath path = base::FilePath(kTerminalRoot).Append(relative_path);
// First look for uncompressed resource, then try for gzipped file.
bool result = base::ReadFileToString(path, &content);
if (!result) {
result =
base::ReadFileToString(base::FilePath(path.value() + ".gz"), &content);
std::string uncompressed;
result = compression::GzipUncompress(content, &uncompressed);
content = std::move(uncompressed);
}
// Terminal gets files from /usr/share/chromeos-assets/crosh-builtin.
// In chromium tests, these files don't exist, so we serve dummy values.
if (!result) {
static const base::NoDestructor<base::flat_map<std::string, std::string>>
kTestFiles({
{"html/crosh.html", ""},
{"html/terminal.html", "<script src='/js/terminal.js'></script>"},
{"js/terminal.js",
"chrome.terminalPrivate.openVmshellProcess([], () => {})"},
});
auto it = kTestFiles->find(relative_path);
if (it != kTestFiles->end()) {
content = it->second;
result = true;
}
}
DCHECK(result) << path;
scoped_refptr<base::RefCountedString> response =
base::RefCountedString::TakeString(&content);
std::move(callback).Run(response.get());
}
} // namespace
// static
std::unique_ptr<TerminalSource> TerminalSource::ForCrosh(Profile* profile) {
std::string default_file = "html/crosh.html";
if (base::FeatureList::IsEnabled(chromeos::features::kCroshSWA)) {
default_file = "html/terminal.html";
}
return base::WrapUnique(new TerminalSource(
profile, chrome::kChromeUIUntrustedCroshURL, default_file));
}
// static
std::unique_ptr<TerminalSource> TerminalSource::ForTerminal(Profile* profile) {
return base::WrapUnique(new TerminalSource(
profile, chrome::kChromeUIUntrustedTerminalURL, "html/terminal.html"));
}
TerminalSource::TerminalSource(Profile* profile,
std::string source,
std::string default_file)
: profile_(profile), source_(source), default_file_(default_file) {
auto* webui_allowlist = WebUIAllowlist::GetOrCreate(profile);
const url::Origin terminal_origin = url::Origin::Create(GURL(source));
CHECK(!terminal_origin.opaque());
for (auto permission :
{ContentSettingsType::JAVASCRIPT, ContentSettingsType::NOTIFICATIONS,
ContentSettingsType::CLIPBOARD_READ_WRITE, ContentSettingsType::COOKIES,
ContentSettingsType::IMAGES, ContentSettingsType::SOUND}) {
webui_allowlist->RegisterAutoGrantedPermission(terminal_origin, permission);
}
}
TerminalSource::~TerminalSource() = default;
std::string TerminalSource::GetSource() {
return source_;
}
#if !BUILDFLAG(OPTIMIZE_WEBUI)
bool TerminalSource::AllowCaching() {
return false;
}
#endif
void TerminalSource::StartDataRequest(
const GURL& url,
const content::WebContents::Getter& wc_getter,
content::URLDataSource::GotDataCallback callback) {
// skip first '/' in path.
std::string path = url.path().substr(1);
if (path.empty())
path = default_file_;
// Replace $i8n{themeColor} in *.html.
if (base::EndsWith(path, ".html", base::CompareCase::INSENSITIVE_ASCII)) {
replacements_["themeColor"] = net::EscapeForHTML(
crostini::GetTerminalSettingBackgroundColor(profile_));
}
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindOnce(&ReadFile, path, std::move(callback)));
}
std::string TerminalSource::GetMimeType(const std::string& path) {
std::string mime_type(kDefaultMime);
std::string ext = base::FilePath(path).Extension();
if (!ext.empty())
net::GetWellKnownMimeTypeFromExtension(ext.substr(1), &mime_type);
return mime_type;
}
bool TerminalSource::ShouldServeMimeTypeAsContentTypeHeader() {
// TerminalSource pages include js modules which require an explicit MimeType.
return true;
}
const ui::TemplateReplacements* TerminalSource::GetReplacements() {
return &replacements_;
}
std::string TerminalSource::GetContentSecurityPolicy(
network::mojom::CSPDirectiveName directive) {
switch (directive) {
case network::mojom::CSPDirectiveName::ImgSrc:
return "img-src * data: blob:;";
case network::mojom::CSPDirectiveName::MediaSrc:
return "media-src data:;";
case network::mojom::CSPDirectiveName::StyleSrc:
return "style-src * 'unsafe-inline'; font-src *;";
case network::mojom::CSPDirectiveName::RequireTrustedTypesFor:
FALLTHROUGH;
case network::mojom::CSPDirectiveName::TrustedTypes:
// TODO(crbug.com/1098685): Trusted Type remaining WebUI
// This removes require-trusted-types-for and trusted-types directives
// from the CSP header.
return std::string();
default:
return content::URLDataSource::GetContentSecurityPolicy(directive);
}
}