1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/webui/test_data_source.h"
6 
7 #include <memory>
8 
9 #include "base/base_paths.h"
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/memory/ref_counted_memory.h"
13 #include "base/path_service.h"
14 #include "base/strings/string_util.h"
15 #include "base/task/post_task.h"
16 #include "base/task/task_traits.h"
17 #include "base/task/thread_pool.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/common/chrome_paths.h"
20 #include "chrome/common/url_constants.h"
21 #include "chrome/common/webui_url_constants.h"
22 #include "content/public/browser/url_data_source.h"
23 #include "content/public/common/url_constants.h"
24 #include "services/network/public/mojom/content_security_policy.mojom.h"
25 
26 namespace {
27 const char kModuleQuery[] = "module=";
28 }  // namespace
29 
TestDataSource(std::string root)30 TestDataSource::TestDataSource(std::string root) {
31   base::FilePath test_data;
32   CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data));
33   src_root_ = test_data.AppendASCII(root).NormalizePathSeparators();
34   DCHECK(test_data.IsParent(src_root_));
35 
36   base::FilePath exe_dir;
37   base::PathService::Get(base::DIR_EXE, &exe_dir);
38   gen_root_ = exe_dir.AppendASCII("gen/chrome/test/data/" + root)
39                   .NormalizePathSeparators();
40   DCHECK(exe_dir.IsParent(gen_root_));
41 }
42 
GetSource()43 std::string TestDataSource::GetSource() {
44   return "test";
45 }
46 
StartDataRequest(const GURL & url,const content::WebContents::Getter & wc_getter,content::URLDataSource::GotDataCallback callback)47 void TestDataSource::StartDataRequest(
48     const GURL& url,
49     const content::WebContents::Getter& wc_getter,
50     content::URLDataSource::GotDataCallback callback) {
51   const std::string path = content::URLDataSource::URLToRequestPath(url);
52   base::ThreadPool::PostTask(
53       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
54       base::BindOnce(&TestDataSource::ReadFile, base::Unretained(this), path,
55                      std::move(callback)));
56 }
57 
GetMimeType(const std::string & path)58 std::string TestDataSource::GetMimeType(const std::string& path) {
59   if (base::EndsWith(path, ".html", base::CompareCase::INSENSITIVE_ASCII) ||
60       base::StartsWith(GetURLForPath(path).query(), kModuleQuery,
61                        base::CompareCase::INSENSITIVE_ASCII)) {
62     // Direct request for HTML, or autogenerated HTML response for module query.
63     return "text/html";
64   }
65   // The test data source currently only serves HTML and JS.
66   CHECK(base::EndsWith(path, ".js", base::CompareCase::INSENSITIVE_ASCII))
67       << "Tried to read file with unexpected type from test data source: "
68       << path;
69   return "application/javascript";
70 }
71 
ShouldServeMimeTypeAsContentTypeHeader()72 bool TestDataSource::ShouldServeMimeTypeAsContentTypeHeader() {
73   return true;
74 }
75 
AllowCaching()76 bool TestDataSource::AllowCaching() {
77   return false;
78 }
79 
GetContentSecurityPolicy(network::mojom::CSPDirectiveName directive)80 std::string TestDataSource::GetContentSecurityPolicy(
81     network::mojom::CSPDirectiveName directive) {
82   if (directive == network::mojom::CSPDirectiveName::ScriptSrc) {
83     return "script-src chrome://* 'self';";
84   } else if (directive == network::mojom::CSPDirectiveName::WorkerSrc) {
85     return "worker-src blob: 'self';";
86   } else if (directive ==
87                  network::mojom::CSPDirectiveName::RequireTrustedTypesFor ||
88              directive == network::mojom::CSPDirectiveName::TrustedTypes) {
89     return std::string();
90   }
91 
92   return content::URLDataSource::GetContentSecurityPolicy(directive);
93 }
94 
GetURLForPath(const std::string & path)95 GURL TestDataSource::GetURLForPath(const std::string& path) {
96   return GURL(std::string(content::kChromeUIScheme) + "://" + GetSource() +
97               "/" + path);
98 }
99 
ReadFile(const std::string & path,content::URLDataSource::GotDataCallback callback)100 void TestDataSource::ReadFile(
101     const std::string& path,
102     content::URLDataSource::GotDataCallback callback) {
103   std::string content;
104 
105   GURL url = GetURLForPath(path);
106   CHECK(url.is_valid());
107   if (url.path() == "/chai.js") {
108     base::FilePath src_root;
109     CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_root));
110     base::FilePath file_path =
111         src_root.AppendASCII("third_party/chaijs/chai.js")
112             .NormalizePathSeparators();
113     CHECK(base::ReadFileToString(file_path, &content))
114         << url.spec() << "=" << file_path.value();
115     scoped_refptr<base::RefCountedString> response =
116         base::RefCountedString::TakeString(&content);
117     std::move(callback).Run(response.get());
118     return;
119   }
120 
121   if (base::StartsWith(url.query(), kModuleQuery,
122                        base::CompareCase::INSENSITIVE_ASCII)) {
123     std::string js_path = url.query().substr(strlen(kModuleQuery));
124 
125     base::FilePath file_path =
126         src_root_.Append(base::FilePath::FromUTF8Unsafe(js_path));
127     // Do some basic validation of the JS file path provided in the query.
128     CHECK_EQ(file_path.Extension(), FILE_PATH_LITERAL(".js"));
129 
130     base::FilePath file_path2 =
131         gen_root_.Append(base::FilePath::FromUTF8Unsafe(js_path));
132     CHECK(base::PathExists(file_path) || base::PathExists(file_path2))
133         << url.spec() << "=" << file_path.value();
134     content = "<script type=\"module\" src=\"" + js_path + "\"></script>";
135   } else {
136     // Try the |src_root_| folder first.
137     base::FilePath file_path =
138         src_root_.Append(base::FilePath::FromUTF8Unsafe(path));
139     if (base::PathExists(file_path)) {
140       CHECK(base::ReadFileToString(file_path, &content))
141           << url.spec() << "=" << file_path.value();
142     } else {
143       // Then try the |gen_root_| folder, covering cases where the test file is
144       // generated at build time.
145       base::FilePath file_path =
146           gen_root_.Append(base::FilePath::FromUTF8Unsafe(path));
147       CHECK(base::ReadFileToString(file_path, &content))
148           << url.spec() << "=" << file_path.value();
149     }
150   }
151 
152   scoped_refptr<base::RefCountedString> response =
153       base::RefCountedString::TakeString(&content);
154   std::move(callback).Run(response.get());
155 }
156