blob: 4b636f511599e4669423d866d35ff0826ec0889a [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/ui/webui/test_data_source.h"
#include <memory>
#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/webui_url_constants.h"
#include "content/public/browser/url_data_source.h"
#include "content/public/common/url_constants.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
namespace {
const char kModuleQuery[] = "module=";
} // namespace
TestDataSource::TestDataSource(std::string root) {
base::FilePath test_data;
CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data));
src_root_ = test_data.AppendASCII(root).NormalizePathSeparators();
DCHECK(test_data.IsParent(src_root_));
base::FilePath exe_dir;
base::PathService::Get(base::DIR_EXE, &exe_dir);
gen_root_ = exe_dir.AppendASCII("gen/chrome/test/data/" + root)
.NormalizePathSeparators();
DCHECK(exe_dir.IsParent(gen_root_));
custom_paths_ = {
{"/chai.js", "third_party/chaijs/chai.js"},
{"/mocha.js", "third_party/mocha/mocha.js"},
{"/test_loader.html", "ui/webui/resources/html/test_loader.html"},
{"/test_loader.js", "ui/webui/resources/js/test_loader.js"},
{"/test_loader_util.js", "ui/webui/resources/js/test_loader_util.js"},
};
}
TestDataSource::~TestDataSource() = default;
std::string TestDataSource::GetSource() {
return "test";
}
void TestDataSource::StartDataRequest(
const GURL& url,
const content::WebContents::Getter& wc_getter,
content::URLDataSource::GotDataCallback callback) {
const std::string path = content::URLDataSource::URLToRequestPath(url);
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindOnce(&TestDataSource::ReadFile, base::Unretained(this), path,
std::move(callback)));
}
std::string TestDataSource::GetMimeType(const std::string& path) {
std::string clean_path = GetURLForPath(path).path();
if (base::EndsWith(clean_path, ".html",
base::CompareCase::INSENSITIVE_ASCII) ||
base::StartsWith(GetURLForPath(path).query(), kModuleQuery,
base::CompareCase::INSENSITIVE_ASCII)) {
// Direct request for HTML, or autogenerated HTML response for module query.
return "text/html";
}
// The test data source currently only serves HTML and JS.
CHECK(base::EndsWith(clean_path, ".js", base::CompareCase::INSENSITIVE_ASCII))
<< "Tried to read file with unexpected type from test data source: "
<< path;
return "application/javascript";
}
bool TestDataSource::ShouldServeMimeTypeAsContentTypeHeader() {
return true;
}
bool TestDataSource::AllowCaching() {
return false;
}
std::string TestDataSource::GetContentSecurityPolicy(
network::mojom::CSPDirectiveName directive) {
if (directive == network::mojom::CSPDirectiveName::ScriptSrc) {
return "script-src chrome://* 'self';";
} else if (directive == network::mojom::CSPDirectiveName::WorkerSrc) {
return "worker-src blob: 'self';";
} else if (directive ==
network::mojom::CSPDirectiveName::RequireTrustedTypesFor ||
directive == network::mojom::CSPDirectiveName::TrustedTypes) {
return std::string();
} else if (directive == network::mojom::CSPDirectiveName::FrameAncestors) {
return "frame-ancestors chrome://* 'self';";
} else if (directive == network::mojom::CSPDirectiveName::FrameSrc) {
return "frame-src chrome://test/;";
} else if (directive == network::mojom::CSPDirectiveName::ChildSrc) {
return "child-src chrome://test/;";
}
return content::URLDataSource::GetContentSecurityPolicy(directive);
}
GURL TestDataSource::GetURLForPath(const std::string& path) {
return GURL(std::string(content::kChromeUIScheme) + "://" + GetSource() +
"/" + path);
}
void TestDataSource::ReadFile(
const std::string& path,
content::URLDataSource::GotDataCallback callback) {
std::string content;
GURL url = GetURLForPath(path);
CHECK(url.is_valid());
// First check if a custom path mapping exists for the requested URL.
auto it = custom_paths_.find(url.path());
if (it != custom_paths_.end()) {
base::FilePath src_root;
CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_root));
base::FilePath file_path =
src_root.AppendASCII(it->second).NormalizePathSeparators();
CHECK(base::ReadFileToString(file_path, &content))
<< url.spec() << "=" << file_path.value();
scoped_refptr<base::RefCountedString> response =
base::RefCountedString::TakeString(&content);
std::move(callback).Run(response.get());
return;
}
if (base::StartsWith(url.query(), kModuleQuery,
base::CompareCase::INSENSITIVE_ASCII)) {
std::string js_path = url.query().substr(strlen(kModuleQuery));
base::FilePath file_path =
src_root_.Append(base::FilePath::FromUTF8Unsafe(js_path));
// Do some basic validation of the JS file path provided in the query.
CHECK_EQ(file_path.Extension(), FILE_PATH_LITERAL(".js"));
base::FilePath file_path2 =
gen_root_.Append(base::FilePath::FromUTF8Unsafe(js_path));
CHECK(base::PathExists(file_path) || base::PathExists(file_path2))
<< url.spec() << "=" << file_path.value();
content = "<script type=\"module\" src=\"" + js_path + "\"></script>";
} else {
// Strip the query
size_t query_length = url.query().length();
std::string no_query_path =
query_length == 0 ? path
: path.substr(0, path.length() - query_length - 1);
// Try the |gen_root_| folder first, covering cases where the test file is
// generated at build time. We do this first as if a test file exists under
// the same name in the src and gen directories, the generated file is
// generally the desired file (for example, may have been preprocessed).
base::FilePath gen_root_file_path =
gen_root_.Append(base::FilePath::FromUTF8Unsafe(no_query_path));
if (base::PathExists(gen_root_file_path)) {
CHECK(base::ReadFileToString(gen_root_file_path, &content))
<< url.spec() << "=" << gen_root_file_path.value();
} else {
// Then try the |src_root_| folder, covering cases where the test file is
// generated at build time.
base::FilePath src_root_file_path =
src_root_.Append(base::FilePath::FromUTF8Unsafe(no_query_path));
CHECK(base::ReadFileToString(src_root_file_path, &content))
<< url.spec() << "=" << src_root_file_path.value();
}
}
scoped_refptr<base::RefCountedString> response =
base::RefCountedString::TakeString(&content);
std::move(callback).Run(response.get());
}