1 // Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4
5 #include "tests/shared/browser/extension_util.h"
6
7 #include <algorithm>
8 #include <memory>
9
10 #include "include/base/cef_callback.h"
11 #include "include/base/cef_cxx17_backports.h"
12 #include "include/cef_parser.h"
13 #include "include/cef_path_util.h"
14 #include "include/wrapper/cef_closure_task.h"
15 #include "tests/shared/browser/file_util.h"
16 #include "tests/shared/browser/resource_util.h"
17
18 namespace client {
19 namespace extension_util {
20
21 namespace {
22
GetResourcesPath()23 std::string GetResourcesPath() {
24 CefString resources_dir;
25 if (CefGetPath(PK_DIR_RESOURCES, resources_dir) && !resources_dir.empty()) {
26 return resources_dir.ToString() + file_util::kPathSep;
27 }
28 return std::string();
29 }
30
31 // Internal extension paths may be prefixed with PK_DIR_RESOURCES and always
32 // use forward slash as path separator.
GetInternalPath(const std::string & extension_path)33 std::string GetInternalPath(const std::string& extension_path) {
34 std::string resources_path_lower = GetResourcesPath();
35 std::string extension_path_lower = extension_path;
36
37 #if defined(OS_WIN)
38 // Convert to lower-case, since Windows paths are case-insensitive.
39 std::transform(resources_path_lower.begin(), resources_path_lower.end(),
40 resources_path_lower.begin(), ::tolower);
41 std::transform(extension_path_lower.begin(), extension_path_lower.end(),
42 extension_path_lower.begin(), ::tolower);
43 #endif
44
45 std::string internal_path;
46 if (!resources_path_lower.empty() &&
47 extension_path_lower.find(resources_path_lower) == 0U) {
48 internal_path = extension_path.substr(resources_path_lower.size());
49 } else {
50 internal_path = extension_path;
51 }
52
53 #if defined(OS_WIN)
54 // Normalize path separators.
55 std::replace(internal_path.begin(), internal_path.end(), '\\', '/');
56 #endif
57
58 return internal_path;
59 }
60
61 using ManifestCallback =
62 base::OnceCallback<void(CefRefPtr<CefDictionaryValue> /*manifest*/)>;
63
RunManifestCallback(ManifestCallback callback,CefRefPtr<CefDictionaryValue> manifest)64 void RunManifestCallback(ManifestCallback callback,
65 CefRefPtr<CefDictionaryValue> manifest) {
66 if (!CefCurrentlyOn(TID_UI)) {
67 // Execute on the browser UI thread.
68 CefPostTask(TID_UI, base::BindOnce(std::move(callback), manifest));
69 return;
70 }
71 std::move(callback).Run(manifest);
72 }
73
74 // Asynchronously reads the manifest and executes |callback| on the UI thread.
GetInternalManifest(const std::string & extension_path,ManifestCallback callback)75 void GetInternalManifest(const std::string& extension_path,
76 ManifestCallback callback) {
77 if (!CefCurrentlyOn(TID_FILE_USER_BLOCKING)) {
78 // Execute on the browser FILE thread.
79 CefPostTask(TID_FILE_USER_BLOCKING,
80 base::BindOnce(GetInternalManifest, extension_path,
81 std::move(callback)));
82 return;
83 }
84
85 const std::string& manifest_path = GetInternalExtensionResourcePath(
86 file_util::JoinPath(extension_path, "manifest.json"));
87 std::string manifest_contents;
88 if (!LoadBinaryResource(manifest_path.c_str(), manifest_contents) ||
89 manifest_contents.empty()) {
90 LOG(ERROR) << "Failed to load manifest from " << manifest_path;
91 RunManifestCallback(std::move(callback), nullptr);
92 return;
93 }
94
95 CefString error_msg;
96 CefRefPtr<CefValue> value =
97 CefParseJSONAndReturnError(manifest_contents, JSON_PARSER_RFC, error_msg);
98 if (!value || value->GetType() != VTYPE_DICTIONARY) {
99 if (error_msg.empty())
100 error_msg = "Incorrectly formatted dictionary contents.";
101 LOG(ERROR) << "Failed to parse manifest from " << manifest_path << "; "
102 << error_msg.ToString();
103 RunManifestCallback(std::move(callback), nullptr);
104 return;
105 }
106
107 RunManifestCallback(std::move(callback), value->GetDictionary());
108 }
109
LoadExtensionWithManifest(CefRefPtr<CefRequestContext> request_context,const std::string & extension_path,CefRefPtr<CefExtensionHandler> handler,CefRefPtr<CefDictionaryValue> manifest)110 void LoadExtensionWithManifest(CefRefPtr<CefRequestContext> request_context,
111 const std::string& extension_path,
112 CefRefPtr<CefExtensionHandler> handler,
113 CefRefPtr<CefDictionaryValue> manifest) {
114 CEF_REQUIRE_UI_THREAD();
115
116 // Load the extension internally. Resource requests will be handled via
117 // AddInternalExtensionToResourceManager.
118 request_context->LoadExtension(extension_path, manifest, handler);
119 }
120
121 } // namespace
122
IsInternalExtension(const std::string & extension_path)123 bool IsInternalExtension(const std::string& extension_path) {
124 // List of internally handled extensions.
125 static const char* extensions[] = {"set_page_color"};
126
127 const std::string& internal_path = GetInternalPath(extension_path);
128 for (size_t i = 0; i < base::size(extensions); ++i) {
129 // Exact match or first directory component.
130 const std::string& extension = extensions[i];
131 if (internal_path == extension ||
132 internal_path.find(extension + '/') == 0) {
133 return true;
134 }
135 }
136
137 return false;
138 }
139
GetInternalExtensionResourcePath(const std::string & extension_path)140 std::string GetInternalExtensionResourcePath(
141 const std::string& extension_path) {
142 return "extensions/" + GetInternalPath(extension_path);
143 }
144
GetExtensionResourcePath(const std::string & extension_path,bool * internal)145 std::string GetExtensionResourcePath(const std::string& extension_path,
146 bool* internal) {
147 const bool is_internal = IsInternalExtension(extension_path);
148 if (internal)
149 *internal = is_internal;
150 if (is_internal)
151 return GetInternalExtensionResourcePath(extension_path);
152 return extension_path;
153 }
154
GetExtensionResourceContents(const std::string & extension_path,std::string & contents)155 bool GetExtensionResourceContents(const std::string& extension_path,
156 std::string& contents) {
157 CEF_REQUIRE_FILE_USER_BLOCKING_THREAD();
158
159 if (IsInternalExtension(extension_path)) {
160 const std::string& contents_path =
161 GetInternalExtensionResourcePath(extension_path);
162 return LoadBinaryResource(contents_path.c_str(), contents);
163 }
164
165 return file_util::ReadFileToString(extension_path, &contents);
166 }
167
LoadExtension(CefRefPtr<CefRequestContext> request_context,const std::string & extension_path,CefRefPtr<CefExtensionHandler> handler)168 void LoadExtension(CefRefPtr<CefRequestContext> request_context,
169 const std::string& extension_path,
170 CefRefPtr<CefExtensionHandler> handler) {
171 if (!CefCurrentlyOn(TID_UI)) {
172 // Execute on the browser UI thread.
173 CefPostTask(TID_UI, base::BindOnce(LoadExtension, request_context,
174 extension_path, handler));
175 return;
176 }
177
178 if (IsInternalExtension(extension_path)) {
179 // Read the extension manifest and load asynchronously.
180 GetInternalManifest(
181 extension_path,
182 base::BindOnce(LoadExtensionWithManifest, request_context,
183 extension_path, handler));
184 } else {
185 // Load the extension from disk.
186 request_context->LoadExtension(extension_path, nullptr, handler);
187 }
188 }
189
AddInternalExtensionToResourceManager(CefRefPtr<CefExtension> extension,CefRefPtr<CefResourceManager> resource_manager)190 void AddInternalExtensionToResourceManager(
191 CefRefPtr<CefExtension> extension,
192 CefRefPtr<CefResourceManager> resource_manager) {
193 DCHECK(IsInternalExtension(extension->GetPath()));
194
195 if (!CefCurrentlyOn(TID_IO)) {
196 // Execute on the browser IO thread.
197 CefPostTask(TID_IO, base::BindOnce(AddInternalExtensionToResourceManager,
198 extension, resource_manager));
199 return;
200 }
201
202 const std::string& origin = GetExtensionOrigin(extension->GetIdentifier());
203 const std::string& resource_path =
204 GetInternalExtensionResourcePath(extension->GetPath());
205
206 // Add provider for bundled resource files.
207 #if defined(OS_WIN)
208 // Read resources from the binary.
209 resource_manager->AddProvider(
210 CreateBinaryResourceProvider(origin, resource_path), 50, std::string());
211 #elif defined(OS_POSIX)
212 // Read resources from a directory on disk.
213 std::string resource_dir;
214 if (GetResourceDir(resource_dir)) {
215 resource_dir += "/" + resource_path;
216 resource_manager->AddDirectoryProvider(origin, resource_dir, 50,
217 std::string());
218 }
219 #endif
220 }
221
GetExtensionOrigin(const std::string & extension_id)222 std::string GetExtensionOrigin(const std::string& extension_id) {
223 return "chrome-extension://" + extension_id + "/";
224 }
225
GetExtensionURL(CefRefPtr<CefExtension> extension)226 std::string GetExtensionURL(CefRefPtr<CefExtension> extension) {
227 CefRefPtr<CefDictionaryValue> browser_action =
228 extension->GetManifest()->GetDictionary("browser_action");
229 if (browser_action) {
230 const std::string& default_popup =
231 browser_action->GetString("default_popup");
232 if (!default_popup.empty())
233 return GetExtensionOrigin(extension->GetIdentifier()) + default_popup;
234 }
235
236 return std::string();
237 }
238
GetExtensionIconPath(CefRefPtr<CefExtension> extension,bool * internal)239 std::string GetExtensionIconPath(CefRefPtr<CefExtension> extension,
240 bool* internal) {
241 CefRefPtr<CefDictionaryValue> browser_action =
242 extension->GetManifest()->GetDictionary("browser_action");
243 if (browser_action) {
244 const std::string& default_icon = browser_action->GetString("default_icon");
245 if (!default_icon.empty()) {
246 return GetExtensionResourcePath(
247 file_util::JoinPath(extension->GetPath(), default_icon), internal);
248 }
249 }
250
251 return std::string();
252 }
253
254 } // namespace extension_util
255 } // namespace client
256