| 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 |