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 
25 namespace {
26 const char kModuleQuery[] = "module=";
27 }  // namespace
28 
TestDataSource(std::string root)29 TestDataSource::TestDataSource(std::string root) {
30   base::FilePath test_data;
31   CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data));
32   src_root_ = test_data.AppendASCII(root).NormalizePathSeparators();
33   DCHECK(test_data.IsParent(src_root_));
34 
35   base::FilePath exe_dir;
36   base::PathService::Get(base::DIR_EXE, &exe_dir);
37   gen_root_ = exe_dir.AppendASCII("gen/chrome/test/data/" + root)
38                   .NormalizePathSeparators();
39   DCHECK(exe_dir.IsParent(gen_root_));
40 }
41 
GetSource()42 std::string TestDataSource::GetSource() {
43   return "test";
44 }
45 
StartDataRequest(const GURL & url,const content::WebContents::Getter & wc_getter,content::URLDataSource::GotDataCallback callback)46 void TestDataSource::StartDataRequest(
47     const GURL& url,
48     const content::WebContents::Getter& wc_getter,
49     content::URLDataSource::GotDataCallback callback) {
50   const std::string path = content::URLDataSource::URLToRequestPath(url);
51   base::ThreadPool::PostTask(
52       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
53       base::BindOnce(&TestDataSource::ReadFile, base::Unretained(this), path,
54                      std::move(callback)));
55 }
56 
GetMimeType(const std::string & path)57 std::string TestDataSource::GetMimeType(const std::string& path) {
58   if (base::EndsWith(path, ".html", base::CompareCase::INSENSITIVE_ASCII) ||
59       base::StartsWith(GetURLForPath(path).query(), kModuleQuery,
60                        base::CompareCase::INSENSITIVE_ASCII)) {
61     // Direct request for HTML, or autogenerated HTML response for module query.
62     return "text/html";
63   }
64   // The test data source currently only serves HTML and JS.
65   CHECK(base::EndsWith(path, ".js", base::CompareCase::INSENSITIVE_ASCII))
66       << "Tried to read file with unexpected type from test data source: "
67       << path;
68   return "application/javascript";
69 }
70 
ShouldServeMimeTypeAsContentTypeHeader()71 bool TestDataSource::ShouldServeMimeTypeAsContentTypeHeader() {
72   return true;
73 }
74 
AllowCaching()75 bool TestDataSource::AllowCaching() {
76   return false;
77 }
78 
GetContentSecurityPolicyScriptSrc()79 std::string TestDataSource::GetContentSecurityPolicyScriptSrc() {
80   return "script-src chrome://* 'self';";
81 }
82 
GetURLForPath(const std::string & path)83 GURL TestDataSource::GetURLForPath(const std::string& path) {
84   return GURL(std::string(content::kChromeUIScheme) + "://" + GetSource() +
85               "/" + path);
86 }
87 
ReadFile(const std::string & path,content::URLDataSource::GotDataCallback callback)88 void TestDataSource::ReadFile(
89     const std::string& path,
90     content::URLDataSource::GotDataCallback callback) {
91   std::string content;
92 
93   GURL url = GetURLForPath(path);
94   CHECK(url.is_valid());
95   if (base::StartsWith(url.query(), kModuleQuery,
96                        base::CompareCase::INSENSITIVE_ASCII)) {
97     std::string js_path = url.query().substr(strlen(kModuleQuery));
98 
99     base::FilePath file_path =
100         src_root_.Append(base::FilePath::FromUTF8Unsafe(js_path));
101     // Do some basic validation of the JS file path provided in the query.
102     CHECK_EQ(file_path.Extension(), FILE_PATH_LITERAL(".js"));
103 
104     base::FilePath file_path2 =
105         gen_root_.Append(base::FilePath::FromUTF8Unsafe(js_path));
106     CHECK(base::PathExists(file_path) || base::PathExists(file_path2))
107         << url.spec() << "=" << file_path.value();
108     content = "<script type=\"module\" src=\"" + js_path + "\"></script>";
109   } else {
110     // Try the |src_root_| folder first.
111     base::FilePath file_path =
112         src_root_.Append(base::FilePath::FromUTF8Unsafe(path));
113     if (base::PathExists(file_path)) {
114       CHECK(base::ReadFileToString(file_path, &content))
115           << url.spec() << "=" << file_path.value();
116     } else {
117       // Then try the |gen_root_| folder, covering cases where the test file is
118       // generated at build time.
119       base::FilePath file_path =
120           gen_root_.Append(base::FilePath::FromUTF8Unsafe(path));
121       CHECK(base::ReadFileToString(file_path, &content))
122           << url.spec() << "=" << file_path.value();
123     }
124   }
125 
126   scoped_refptr<base::RefCountedString> response =
127       base::RefCountedString::TakeString(&content);
128   std::move(callback).Run(response.get());
129 }
130