OLD | NEW |
| (Empty) |
1 // Copyright 2015 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 "mojo/shell/package_manager/package_manager_impl.h" | |
6 | |
7 #include <stdint.h> | |
8 | |
9 #include <utility> | |
10 | |
11 #include "base/bind.h" | |
12 #include "base/json/json_file_value_serializer.h" | |
13 #include "base/logging.h" | |
14 #include "base/strings/utf_string_conversions.h" | |
15 #include "base/task_runner_util.h" | |
16 #include "base/values.h" | |
17 #include "mojo/shell/application_manager.h" | |
18 #include "mojo/shell/connect_util.h" | |
19 #include "mojo/shell/fetcher/about_fetcher.h" | |
20 #include "mojo/shell/fetcher/data_fetcher.h" | |
21 #include "mojo/shell/fetcher/local_fetcher.h" | |
22 #include "mojo/shell/fetcher/network_fetcher.h" | |
23 #include "mojo/shell/fetcher/switches.h" | |
24 #include "mojo/shell/package_manager/content_handler_connection.h" | |
25 #include "mojo/shell/public/interfaces/content_handler.mojom.h" | |
26 #include "mojo/shell/query_util.h" | |
27 #include "mojo/shell/switches.h" | |
28 #include "mojo/util/filename_util.h" | |
29 #include "net/base/filename_util.h" | |
30 #include "url/gurl.h" | |
31 | |
32 namespace mojo { | |
33 namespace shell { | |
34 namespace { | |
35 | |
36 CapabilityFilter BuildCapabilityFilterFromDictionary( | |
37 const base::DictionaryValue& value) { | |
38 CapabilityFilter filter; | |
39 base::DictionaryValue::Iterator it(value); | |
40 for (; !it.IsAtEnd(); it.Advance()) { | |
41 const base::ListValue* values = nullptr; | |
42 CHECK(it.value().GetAsList(&values)); | |
43 AllowedInterfaces interfaces; | |
44 for (auto i = values->begin(); i != values->end(); ++i) { | |
45 std::string iface_name; | |
46 const base::Value* v = *i; | |
47 CHECK(v->GetAsString(&iface_name)); | |
48 interfaces.insert(iface_name); | |
49 } | |
50 filter[it.key()] = interfaces; | |
51 } | |
52 return filter; | |
53 } | |
54 | |
55 ApplicationInfo BuildApplicationInfoFromDictionary( | |
56 const base::DictionaryValue& value) { | |
57 ApplicationInfo info; | |
58 CHECK(value.GetString("url", &info.url)); | |
59 CHECK(value.GetString("name", &info.name)); | |
60 const base::DictionaryValue* capabilities = nullptr; | |
61 CHECK(value.GetDictionary("capabilities", &capabilities)); | |
62 info.base_filter = BuildCapabilityFilterFromDictionary(*capabilities); | |
63 return info; | |
64 } | |
65 | |
66 void SerializeEntry(const ApplicationInfo& entry, | |
67 base::DictionaryValue** value) { | |
68 *value = new base::DictionaryValue; | |
69 (*value)->SetString("url", entry.url); | |
70 (*value)->SetString("name", entry.name); | |
71 base::DictionaryValue* capabilities = new base::DictionaryValue; | |
72 for (const auto& pair : entry.base_filter) { | |
73 scoped_ptr<base::ListValue> interfaces(new base::ListValue); | |
74 for (const auto& iface_name : pair.second) | |
75 interfaces->AppendString(iface_name); | |
76 capabilities->Set(pair.first, std::move(interfaces)); | |
77 } | |
78 (*value)->Set("capabilities", make_scoped_ptr(capabilities)); | |
79 } | |
80 | |
81 } | |
82 | |
83 ApplicationInfo::ApplicationInfo() {} | |
84 ApplicationInfo::~ApplicationInfo() {} | |
85 | |
86 ApplicationCatalogStore::~ApplicationCatalogStore() {} | |
87 | |
88 PackageManagerImpl::PackageManagerImpl( | |
89 const base::FilePath& shell_file_root, | |
90 base::TaskRunner* task_runner, | |
91 ApplicationCatalogStore* catalog_store) | |
92 : application_manager_(nullptr), | |
93 disable_cache_(base::CommandLine::ForCurrentProcess()->HasSwitch( | |
94 switches::kDisableCache)), | |
95 content_handler_id_counter_(0u), | |
96 task_runner_(task_runner), | |
97 shell_file_root_(shell_file_root), | |
98 catalog_store_(catalog_store) { | |
99 if (!shell_file_root.empty()) { | |
100 GURL mojo_root_file_url = | |
101 util::FilePathToFileURL(shell_file_root).Resolve(std::string()); | |
102 url_resolver_.reset(new URLResolver(mojo_root_file_url)); | |
103 } | |
104 DeserializeCatalog(); | |
105 } | |
106 | |
107 PackageManagerImpl::~PackageManagerImpl() { | |
108 IdentityToContentHandlerMap identity_to_content_handler( | |
109 identity_to_content_handler_); | |
110 for (auto& pair : identity_to_content_handler) | |
111 pair.second->CloseConnection(); | |
112 } | |
113 | |
114 void PackageManagerImpl::RegisterContentHandler( | |
115 const std::string& mime_type, | |
116 const GURL& content_handler_url) { | |
117 DCHECK(content_handler_url.is_valid()) | |
118 << "Content handler URL is invalid for mime type " << mime_type; | |
119 mime_type_to_url_[mime_type] = content_handler_url; | |
120 } | |
121 | |
122 void PackageManagerImpl::RegisterApplicationPackageAlias( | |
123 const GURL& alias, | |
124 const GURL& content_handler_package, | |
125 const std::string& qualifier) { | |
126 application_package_alias_[alias] = | |
127 std::make_pair(content_handler_package, qualifier); | |
128 } | |
129 | |
130 void PackageManagerImpl::SetApplicationManager(ApplicationManager* manager) { | |
131 application_manager_ = manager; | |
132 } | |
133 | |
134 void PackageManagerImpl::BuiltinAppLoaded(const GURL& url) { | |
135 // TODO(beng): Determine if this is in the right place, and block | |
136 // establishing the connection on receiving a complete manifest. | |
137 EnsureURLInCatalog(url); | |
138 } | |
139 | |
140 void PackageManagerImpl::FetchRequest( | |
141 URLRequestPtr request, | |
142 const Fetcher::FetchCallback& loader_callback) { | |
143 GURL url(request->url.get()); | |
144 if (url.SchemeIs(AboutFetcher::kAboutScheme)) { | |
145 AboutFetcher::Start(url, loader_callback); | |
146 return; | |
147 } | |
148 | |
149 if (url.SchemeIs(url::kDataScheme)) { | |
150 DataFetcher::Start(url, loader_callback); | |
151 return; | |
152 } | |
153 | |
154 GURL resolved_url = ResolveURL(url); | |
155 if (resolved_url.SchemeIsFile()) { | |
156 // LocalFetcher uses the network service to infer MIME types from URLs. | |
157 // Skip this for mojo URLs to avoid recursively loading the network service. | |
158 if (!network_service_ && !url.SchemeIs("mojo") && !url.SchemeIs("exe")) { | |
159 ConnectToInterface(application_manager_, GURL("mojo:network_service"), | |
160 &network_service_); | |
161 } | |
162 // Ownership of this object is transferred to |loader_callback|. | |
163 // TODO(beng): this is eff'n weird. | |
164 new LocalFetcher(network_service_.get(), resolved_url, | |
165 GetBaseURLAndQuery(resolved_url, nullptr), | |
166 shell_file_root_, loader_callback); | |
167 | |
168 // TODO(beng): Determine if this is in the right place, and block | |
169 // establishing the connection on receiving a complete manifest. | |
170 EnsureURLInCatalog(url); | |
171 return; | |
172 } | |
173 | |
174 if (!url_loader_factory_) { | |
175 ConnectToInterface(application_manager_, GURL("mojo:network_service"), | |
176 &url_loader_factory_); | |
177 } | |
178 | |
179 // Ownership of this object is transferred to |loader_callback|. | |
180 // TODO(beng): this is eff'n weird. | |
181 new NetworkFetcher(disable_cache_, std::move(request), | |
182 url_loader_factory_.get(), loader_callback); | |
183 } | |
184 | |
185 uint32_t PackageManagerImpl::HandleWithContentHandler( | |
186 Fetcher* fetcher, | |
187 const Identity& source, | |
188 const GURL& target_url, | |
189 const CapabilityFilter& target_filter, | |
190 InterfaceRequest<mojom::ShellClient>* request) { | |
191 Identity content_handler_identity; | |
192 URLResponsePtr response; | |
193 if (ShouldHandleWithContentHandler(fetcher, | |
194 target_url, | |
195 target_filter, | |
196 &content_handler_identity, | |
197 &response)) { | |
198 ContentHandlerConnection* connection = | |
199 GetContentHandler(content_handler_identity, source); | |
200 connection->StartApplication(std::move(*request), std::move(response)); | |
201 return connection->id(); | |
202 } | |
203 return mojom::Shell::kInvalidApplicationID; | |
204 } | |
205 | |
206 bool PackageManagerImpl::IsURLInCatalog(const std::string& url) const { | |
207 return catalog_.find(url) != catalog_.end(); | |
208 } | |
209 | |
210 std::string PackageManagerImpl::GetApplicationName( | |
211 const std::string& url) const { | |
212 auto it = catalog_.find(url); | |
213 return it != catalog_.end() ? it->second.name : url; | |
214 } | |
215 | |
216 GURL PackageManagerImpl::ResolveMojoURL(const GURL& mojo_url) { | |
217 return ResolveURL(mojo_url); | |
218 } | |
219 | |
220 uint32_t PackageManagerImpl::StartContentHandler( | |
221 const Identity& source, | |
222 const Identity& content_handler, | |
223 const GURL& url, | |
224 mojom::ShellClientRequest request) { | |
225 URLResponsePtr response(URLResponse::New()); | |
226 response->url = url.spec(); | |
227 ContentHandlerConnection* connection = | |
228 GetContentHandler(content_handler, source); | |
229 connection->StartApplication(std::move(request), std::move(response)); | |
230 return connection->id(); | |
231 } | |
232 | |
233 GURL PackageManagerImpl::ResolveURL(const GURL& url) { | |
234 return url_resolver_.get() ? url_resolver_->ResolveMojoURL(url) : url; | |
235 } | |
236 | |
237 bool PackageManagerImpl::ShouldHandleWithContentHandler( | |
238 Fetcher* fetcher, | |
239 const GURL& target_url, | |
240 const CapabilityFilter& target_filter, | |
241 Identity* content_handler_identity, | |
242 URLResponsePtr* response) const { | |
243 // TODO(beng): it seems like some delegate should/would want to have a say in | |
244 // configuring the qualifier also. | |
245 // Why can't we use the real qualifier in single process mode? Because of | |
246 // base::AtExitManager. If you link in ApplicationRunner into your code, and | |
247 // then we make initialize multiple copies of the application, we end up | |
248 // with multiple AtExitManagers and will check on the second one being | |
249 // created. | |
250 // | |
251 // Why doesn't that happen when running different apps? Because | |
252 // your_thing.mojo!base::AtExitManager and | |
253 // my_thing.mojo!base::AtExitManager are different symbols. | |
254 bool use_real_qualifier = !base::CommandLine::ForCurrentProcess()->HasSwitch( | |
255 switches::kSingleProcess); | |
256 | |
257 GURL content_handler_url; | |
258 // The response begins with a #!mojo <content-handler-url>. | |
259 std::string shebang; | |
260 if (fetcher->PeekContentHandler(&shebang, &content_handler_url)) { | |
261 *response = fetcher->AsURLResponse(task_runner_, | |
262 static_cast<int>(shebang.size())); | |
263 *content_handler_identity = Identity( | |
264 content_handler_url, | |
265 use_real_qualifier ? (*response)->site.To<std::string>() | |
266 : std::string(), | |
267 target_filter); | |
268 return true; | |
269 } | |
270 | |
271 // The response MIME type matches a registered content handler. | |
272 auto iter = mime_type_to_url_.find(fetcher->MimeType()); | |
273 if (iter != mime_type_to_url_.end()) { | |
274 *response = fetcher->AsURLResponse(task_runner_, 0); | |
275 *content_handler_identity = Identity( | |
276 iter->second, | |
277 use_real_qualifier ? (*response)->site.To<std::string>() | |
278 : std::string(), | |
279 target_filter); | |
280 return true; | |
281 } | |
282 | |
283 // The response URL matches a registered content handler. | |
284 auto alias_iter = application_package_alias_.find(target_url); | |
285 if (alias_iter != application_package_alias_.end()) { | |
286 // We replace the qualifier with the one our package alias requested. | |
287 *response = URLResponse::New(); | |
288 (*response)->url = target_url.spec(); | |
289 *content_handler_identity = Identity( | |
290 alias_iter->second.first, | |
291 use_real_qualifier ? alias_iter->second.second : std::string(), | |
292 target_filter); | |
293 return true; | |
294 } | |
295 | |
296 return false; | |
297 } | |
298 | |
299 ContentHandlerConnection* PackageManagerImpl::GetContentHandler( | |
300 const Identity& content_handler_identity, | |
301 const Identity& source_identity) { | |
302 auto it = identity_to_content_handler_.find(content_handler_identity); | |
303 if (it != identity_to_content_handler_.end()) | |
304 return it->second; | |
305 | |
306 ContentHandlerConnection* connection = new ContentHandlerConnection( | |
307 application_manager_, source_identity, | |
308 content_handler_identity, | |
309 ++content_handler_id_counter_, | |
310 base::Bind(&PackageManagerImpl::OnContentHandlerConnectionClosed, | |
311 base::Unretained(this))); | |
312 identity_to_content_handler_[content_handler_identity] = connection; | |
313 return connection; | |
314 } | |
315 | |
316 void PackageManagerImpl::OnContentHandlerConnectionClosed( | |
317 ContentHandlerConnection* connection) { | |
318 // Remove the mapping. | |
319 auto it = identity_to_content_handler_.find(connection->identity()); | |
320 DCHECK(it != identity_to_content_handler_.end()); | |
321 identity_to_content_handler_.erase(it); | |
322 } | |
323 | |
324 void PackageManagerImpl::EnsureURLInCatalog(const GURL& url) { | |
325 if (IsURLInCatalog(url.spec()) || !url_resolver_) | |
326 return; | |
327 | |
328 GURL manifest_url = url_resolver_->ResolveMojoManifest(url); | |
329 if (manifest_url.is_empty()) | |
330 return; | |
331 base::FilePath manifest_path; | |
332 CHECK(net::FileURLToFilePath(manifest_url, &manifest_path)); | |
333 base::PostTaskAndReplyWithResult( | |
334 task_runner_, FROM_HERE, | |
335 base::Bind(&PackageManagerImpl::ReadManifest, base::Unretained(this), | |
336 manifest_path), | |
337 base::Bind(&PackageManagerImpl::OnReadManifest, | |
338 base::Unretained(this))); | |
339 } | |
340 | |
341 void PackageManagerImpl::DeserializeCatalog() { | |
342 ApplicationInfo info; | |
343 info.url = "mojo://shell/"; | |
344 info.name = "Mojo Shell"; | |
345 catalog_[info.url] = info; | |
346 | |
347 if (!catalog_store_) | |
348 return; | |
349 base::ListValue* catalog = nullptr; | |
350 catalog_store_->GetStore(&catalog); | |
351 CHECK(catalog); | |
352 for (auto it = catalog->begin(); it != catalog->end(); ++it) { | |
353 const base::DictionaryValue* dictionary = nullptr; | |
354 const base::Value* v = *it; | |
355 CHECK(v->GetAsDictionary(&dictionary)); | |
356 DeserializeApplication(dictionary); | |
357 } | |
358 } | |
359 | |
360 void PackageManagerImpl::SerializeCatalog() { | |
361 scoped_ptr<base::ListValue> catalog(new base::ListValue); | |
362 for (const auto& info : catalog_) { | |
363 base::DictionaryValue* dictionary = nullptr; | |
364 SerializeEntry(info.second, &dictionary); | |
365 catalog->Append(make_scoped_ptr(dictionary)); | |
366 } | |
367 if (catalog_store_) | |
368 catalog_store_->UpdateStore(std::move(catalog)); | |
369 } | |
370 | |
371 const ApplicationInfo& PackageManagerImpl::DeserializeApplication( | |
372 const base::DictionaryValue* dictionary) { | |
373 ApplicationInfo info = BuildApplicationInfoFromDictionary(*dictionary); | |
374 // If another app refers to this app, then we already added an entry for | |
375 // |info| as a result of reading the first apps manifest. | |
376 if (catalog_.count(info.url)) | |
377 return catalog_[info.url]; | |
378 | |
379 catalog_[info.url] = info; | |
380 | |
381 if (dictionary->HasKey("applications")) { | |
382 const base::ListValue* applications = nullptr; | |
383 dictionary->GetList("applications", &applications); | |
384 for (size_t i = 0; i < applications->GetSize(); ++i) { | |
385 const base::DictionaryValue* child = nullptr; | |
386 applications->GetDictionary(i, &child); | |
387 const ApplicationInfo& child_info = DeserializeApplication(child); | |
388 GURL child_url(child_info.url); | |
389 RegisterApplicationPackageAlias(child_url, GURL(info.url), | |
390 child_url.host()); | |
391 } | |
392 } | |
393 return catalog_[info.url]; | |
394 } | |
395 | |
396 scoped_ptr<base::Value> PackageManagerImpl::ReadManifest( | |
397 const base::FilePath& manifest_path) { | |
398 JSONFileValueDeserializer deserializer(manifest_path); | |
399 int error = 0; | |
400 std::string message; | |
401 // TODO(beng): probably want to do more detailed error checking. This should | |
402 // be done when figuring out if to unblock connection completion. | |
403 return deserializer.Deserialize(&error, &message); | |
404 } | |
405 | |
406 void PackageManagerImpl::OnReadManifest(scoped_ptr<base::Value> manifest) { | |
407 if (!manifest) | |
408 return; | |
409 | |
410 base::DictionaryValue* dictionary = nullptr; | |
411 CHECK(manifest->GetAsDictionary(&dictionary)); | |
412 DeserializeApplication(dictionary); | |
413 SerializeCatalog(); | |
414 } | |
415 | |
416 } // namespace shell | |
417 } // namespace mojo | |
OLD | NEW |