Upload upstream chromium 76.0.3809.146
[platform/framework/web/chromium-efl.git] / apps / launcher.cc
1 // Copyright 2013 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 "apps/launcher.h"
6
7 #include <memory>
8 #include <set>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/logging.h"
16 #include "base/memory/ref_counted.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/task/post_task.h"
20 #include "base/task/task_traits.h"
21 #include "content/public/browser/browser_context.h"
22 #include "content/public/browser/browser_task_traits.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/common/content_switches.h"
27 #include "content/public/common/url_constants.h"
28 #include "extensions/browser/api/app_runtime/app_runtime_api.h"
29 #include "extensions/browser/api/file_handlers/app_file_handler_util.h"
30 #include "extensions/browser/api/file_handlers/directory_util.h"
31 #include "extensions/browser/api/file_handlers/mime_util.h"
32 #include "extensions/browser/entry_info.h"
33 #include "extensions/browser/event_router.h"
34 #include "extensions/browser/extension_host.h"
35 #include "extensions/browser/extension_prefs.h"
36 #include "extensions/browser/extension_registry.h"
37 #include "extensions/browser/granted_file_entry.h"
38 #include "extensions/browser/lazy_context_id.h"
39 #include "extensions/browser/lazy_context_task_queue.h"
40 #include "extensions/browser/process_manager.h"
41 #include "extensions/common/api/app_runtime.h"
42 #include "extensions/common/extension.h"
43 #include "extensions/common/manifest_handlers/kiosk_mode_info.h"
44 #include "extensions/common/permissions/api_permission.h"
45 #include "extensions/common/permissions/permissions_data.h"
46 #include "net/base/filename_util.h"
47 #include "url/gurl.h"
48
49 #if defined(OS_CHROMEOS)
50 #include "components/user_manager/user_manager.h"
51 #endif
52
53 namespace app_runtime = extensions::api::app_runtime;
54
55 using content::BrowserThread;
56 using extensions::AppRuntimeEventRouter;
57 using extensions::api::app_runtime::PlayStoreStatus;
58 using extensions::app_file_handler_util::CreateFileEntry;
59 using extensions::app_file_handler_util::FileHandlerCanHandleEntry;
60 using extensions::app_file_handler_util::FileHandlerForId;
61 using extensions::app_file_handler_util::HasFileSystemWritePermission;
62 using extensions::app_file_handler_util::PrepareFilesForWritableApp;
63 using extensions::EventRouter;
64 using extensions::Extension;
65 using extensions::ExtensionHost;
66 using extensions::GrantedFileEntry;
67
68 namespace apps {
69
70 namespace {
71
72 const char kFallbackMimeType[] = "application/octet-stream";
73
74 bool DoMakePathAbsolute(const base::FilePath& current_directory,
75                         base::FilePath* file_path) {
76   DCHECK(file_path);
77   if (file_path->IsAbsolute())
78     return true;
79
80   if (current_directory.empty()) {
81     base::FilePath absolute_path = base::MakeAbsoluteFilePath(*file_path);
82     if (absolute_path.empty())
83       return false;
84     *file_path = absolute_path;
85     return true;
86   }
87
88   if (!current_directory.IsAbsolute())
89     return false;
90
91   *file_path = current_directory.Append(*file_path);
92   return true;
93 }
94
95 // Class to handle launching of platform apps to open specific paths.
96 // An instance of this class is created for each launch. The lifetime of these
97 // instances is managed by reference counted pointers. As long as an instance
98 // has outstanding tasks on a message queue it will be retained; once all
99 // outstanding tasks are completed it will be deleted.
100 class PlatformAppPathLauncher
101     : public base::RefCountedThreadSafe<PlatformAppPathLauncher> {
102  public:
103   PlatformAppPathLauncher(content::BrowserContext* context,
104                           const Extension* app,
105                           const std::vector<base::FilePath>& entry_paths)
106       : context_(context),
107         extension_id(app->id()),
108         entry_paths_(entry_paths),
109         mime_type_collector_(context),
110         is_directory_collector_(context) {}
111
112   PlatformAppPathLauncher(content::BrowserContext* context,
113                           const Extension* app,
114                           const base::FilePath& file_path)
115       : context_(context),
116         extension_id(app->id()),
117         mime_type_collector_(context),
118         is_directory_collector_(context) {
119     if (!file_path.empty())
120       entry_paths_.push_back(file_path);
121   }
122
123   void set_action_data(std::unique_ptr<app_runtime::ActionData> action_data) {
124     action_data_ = std::move(action_data);
125   }
126
127   void set_launch_source(extensions::AppLaunchSource launch_source) {
128     launch_source_ = launch_source;
129   }
130
131   void Launch() {
132     DCHECK_CURRENTLY_ON(BrowserThread::UI);
133
134     const Extension* app = GetExtension();
135     if (!app)
136       return;
137
138     if (entry_paths_.empty()) {
139       LaunchWithBasicData();
140       return;
141     }
142
143     for (size_t i = 0; i < entry_paths_.size(); ++i) {
144       DCHECK(entry_paths_[i].IsAbsolute());
145     }
146
147     is_directory_collector_.CollectForEntriesPaths(
148         entry_paths_,
149         base::Bind(&PlatformAppPathLauncher::OnAreDirectoriesCollected, this,
150                    HasFileSystemWritePermission(app)));
151   }
152
153   void LaunchWithHandler(const std::string& handler_id) {
154     handler_id_ = handler_id;
155     Launch();
156   }
157
158   void LaunchWithRelativePath(const base::FilePath& current_directory) {
159     base::PostTaskWithTraits(
160         FROM_HERE,
161         {base::TaskPriority::USER_VISIBLE, base::MayBlock(),
162          base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
163         base::BindOnce(&PlatformAppPathLauncher::MakePathAbsolute, this,
164                        current_directory));
165   }
166
167  private:
168   friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>;
169
170   virtual ~PlatformAppPathLauncher() = default;
171
172   void MakePathAbsolute(const base::FilePath& current_directory) {
173     for (std::vector<base::FilePath>::iterator it = entry_paths_.begin();
174          it != entry_paths_.end(); ++it) {
175       if (!DoMakePathAbsolute(current_directory, &*it)) {
176         LOG(WARNING) << "Cannot make absolute path from " << it->value();
177         base::PostTaskWithTraits(
178             FROM_HERE, {BrowserThread::UI},
179             base::BindOnce(&PlatformAppPathLauncher::LaunchWithBasicData,
180                            this));
181         return;
182       }
183     }
184
185     base::PostTaskWithTraits(
186         FROM_HERE, {BrowserThread::UI},
187         base::BindOnce(&PlatformAppPathLauncher::Launch, this));
188   }
189
190   void OnFilesValid(std::unique_ptr<std::set<base::FilePath>> directory_paths) {
191     mime_type_collector_.CollectForLocalPaths(
192         entry_paths_,
193         base::Bind(
194             &PlatformAppPathLauncher::OnAreDirectoriesAndMimeTypesCollected,
195             this, base::Passed(std::move(directory_paths))));
196   }
197
198   void OnFilesInvalid(const base::FilePath& /* error_path */) {
199     LaunchWithBasicData();
200   }
201
202   void LaunchWithBasicData() {
203     // This method is required as an entry point on the UI thread.
204     DCHECK_CURRENTLY_ON(BrowserThread::UI);
205
206     const Extension* app = GetExtension();
207     if (!app)
208       return;
209
210     std::unique_ptr<app_runtime::LaunchData> launch_data =
211         std::make_unique<app_runtime::LaunchData>();
212     launch_data->action_data = std::move(action_data_);
213     if (!handler_id_.empty())
214       launch_data->id = std::make_unique<std::string>(handler_id_);
215
216     AppRuntimeEventRouter::DispatchOnLaunchedEvent(
217         context_, app, launch_source_, std::move(launch_data));
218   }
219
220   void OnAreDirectoriesCollected(
221       bool has_file_system_write_permission,
222       std::unique_ptr<std::set<base::FilePath>> directory_paths) {
223     if (has_file_system_write_permission) {
224       std::set<base::FilePath>* const directory_paths_ptr =
225           directory_paths.get();
226       PrepareFilesForWritableApp(
227           entry_paths_, context_, *directory_paths_ptr,
228           base::Bind(&PlatformAppPathLauncher::OnFilesValid, this,
229                      base::Passed(std::move(directory_paths))),
230           base::Bind(&PlatformAppPathLauncher::OnFilesInvalid, this));
231       return;
232     }
233
234     OnFilesValid(std::move(directory_paths));
235   }
236
237   void OnAreDirectoriesAndMimeTypesCollected(
238       std::unique_ptr<std::set<base::FilePath>> directory_paths,
239       std::unique_ptr<std::vector<std::string>> mime_types) {
240     DCHECK(entry_paths_.size() == mime_types->size());
241     // If fetching a mime type failed, then use a fallback one.
242     for (size_t i = 0; i < entry_paths_.size(); ++i) {
243       const std::string mime_type =
244           !(*mime_types)[i].empty() ? (*mime_types)[i] : kFallbackMimeType;
245       bool is_directory =
246           directory_paths->find(entry_paths_[i]) != directory_paths->end();
247       entries_.push_back(
248           extensions::EntryInfo(entry_paths_[i], mime_type, is_directory));
249     }
250
251     const Extension* app = GetExtension();
252     if (!app)
253       return;
254
255     // Find file handler from the platform app for the file being opened.
256     const extensions::FileHandlerInfo* handler = NULL;
257     if (!handler_id_.empty()) {
258       handler = FileHandlerForId(*app, handler_id_);
259       if (handler) {
260         for (size_t i = 0; i < entry_paths_.size(); ++i) {
261           if (!FileHandlerCanHandleEntry(*handler, entries_[i])) {
262             LOG(WARNING)
263                 << "Extension does not provide a valid file handler for "
264                 << entry_paths_[i].value();
265             handler = NULL;
266             break;
267           }
268         }
269       }
270     } else {
271       const std::vector<extensions::FileHandlerMatch> handlers =
272           extensions::app_file_handler_util::FindFileHandlerMatchesForEntries(
273               *app, entries_);
274       if (!handlers.empty())
275         handler = handlers[0].handler;
276     }
277
278     // If this app doesn't have a file handler that supports the file, launch
279     // with no launch data.
280     if (!handler) {
281       LOG(WARNING) << "Extension does not provide a valid file handler.";
282       LaunchWithBasicData();
283       return;
284     }
285
286     if (handler_id_.empty())
287       handler_id_ = handler->id;
288
289     // Access needs to be granted to the file for the process associated with
290     // the extension. To do this the ExtensionHost is needed. This might not be
291     // available, or it might be in the process of being unloaded, in which case
292     // the lazy background task queue is used to load the extension and then
293     // call back to us.
294     const extensions::LazyContextId context_id(context_, extension_id);
295     extensions::LazyContextTaskQueue* const queue = context_id.GetTaskQueue();
296     if (queue->ShouldEnqueueTask(context_, app)) {
297       queue->AddPendingTask(
298           context_id,
299           base::Bind(&PlatformAppPathLauncher::GrantAccessToFilesAndLaunch,
300                      this));
301       return;
302     }
303
304     extensions::ProcessManager* const process_manager =
305         extensions::ProcessManager::Get(context_);
306     ExtensionHost* const host =
307         process_manager->GetBackgroundHostForExtension(extension_id);
308     DCHECK(host);
309     GrantAccessToFilesAndLaunch(
310         std::make_unique<extensions::LazyContextTaskQueue::ContextInfo>(host));
311   }
312
313   void GrantAccessToFilesAndLaunch(
314       std::unique_ptr<extensions::LazyContextTaskQueue::ContextInfo>
315           context_info) {
316     const Extension* app = GetExtension();
317     if (!app)
318       return;
319
320     // If there was an error loading the app page, |context_info| will be NULL.
321     if (!context_info) {
322       LOG(ERROR) << "Could not load app page for " << extension_id;
323       return;
324     }
325
326     std::vector<GrantedFileEntry> granted_entries;
327     for (size_t i = 0; i < entry_paths_.size(); ++i) {
328       granted_entries.push_back(CreateFileEntry(
329           context_, app, context_info->render_process_host->GetID(),
330           entries_[i].path, entries_[i].is_directory));
331     }
332
333     AppRuntimeEventRouter::DispatchOnLaunchedEventWithFileEntries(
334         context_, app, launch_source_, handler_id_, entries_, granted_entries,
335         std::move(action_data_));
336   }
337
338   const Extension* GetExtension() const {
339     return extensions::ExtensionRegistry::Get(context_)->GetExtensionById(
340         extension_id, extensions::ExtensionRegistry::EVERYTHING);
341   }
342
343   // The browser context the app should be run in.
344   content::BrowserContext* context_;
345   // The id of the extension providing the app. A pointer to the extension is
346   // not kept as the extension may be unloaded and deleted during the course of
347   // the launch.
348   const std::string extension_id;
349   extensions::AppLaunchSource launch_source_ = extensions::SOURCE_FILE_HANDLER;
350   std::unique_ptr<app_runtime::ActionData> action_data_;
351   // A list of files and directories to be passed through to the app.
352   std::vector<base::FilePath> entry_paths_;
353   // A corresponding list with EntryInfo for every base::FilePath in
354   // entry_paths_.
355   std::vector<extensions::EntryInfo> entries_;
356   // The ID of the file handler used to launch the app.
357   std::string handler_id_;
358   extensions::app_file_handler_util::MimeTypeCollector mime_type_collector_;
359   extensions::app_file_handler_util::IsDirectoryCollector
360       is_directory_collector_;
361
362   DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher);
363 };
364
365 }  // namespace
366
367 void LaunchPlatformAppWithCommandLine(content::BrowserContext* context,
368                                       const extensions::Extension* app,
369                                       const base::CommandLine& command_line,
370                                       const base::FilePath& current_directory,
371                                       extensions::AppLaunchSource source,
372                                       PlayStoreStatus play_store_status) {
373   LaunchPlatformAppWithCommandLineAndLaunchId(context, app, "", command_line,
374                                               current_directory, source,
375                                               play_store_status);
376 }
377
378 void LaunchPlatformAppWithCommandLineAndLaunchId(
379     content::BrowserContext* context,
380     const extensions::Extension* app,
381     const std::string& launch_id,
382     const base::CommandLine& command_line,
383     const base::FilePath& current_directory,
384     extensions::AppLaunchSource source,
385     PlayStoreStatus play_store_status) {
386   // An app with "kiosk_only" should not be installed and launched
387   // outside of ChromeOS kiosk mode in the first place. This is a defensive
388   // check in case this scenario does occur.
389   if (extensions::KioskModeInfo::IsKioskOnly(app)) {
390     bool in_kiosk_mode = false;
391 #if defined(OS_CHROMEOS)
392     user_manager::UserManager* user_manager = user_manager::UserManager::Get();
393     in_kiosk_mode = user_manager && user_manager->IsLoggedInAsKioskApp();
394 #endif
395     if (!in_kiosk_mode) {
396       LOG(ERROR) << "App with 'kiosk_only' attribute must be run in "
397                  << " ChromeOS kiosk mode.";
398       NOTREACHED();
399       return;
400     }
401   }
402
403 #if defined(OS_WIN)
404   base::CommandLine::StringType about_blank_url(
405       base::ASCIIToUTF16(url::kAboutBlankURL));
406 #else
407   base::CommandLine::StringType about_blank_url(url::kAboutBlankURL);
408 #endif
409   base::CommandLine::StringVector args = command_line.GetArgs();
410   // Browser tests will add about:blank to the command line. This should
411   // never be interpreted as a file to open, as doing so with an app that
412   // has write access will result in a file 'about' being created, which
413   // causes problems on the bots.
414   if (args.empty() || (command_line.HasSwitch(switches::kTestType) &&
415                        args[0] == about_blank_url)) {
416     std::unique_ptr<app_runtime::LaunchData> launch_data =
417         std::make_unique<app_runtime::LaunchData>();
418     if (play_store_status != PlayStoreStatus::PLAY_STORE_STATUS_UNKNOWN)
419       launch_data->play_store_status = play_store_status;
420     if (!launch_id.empty())
421       launch_data->id.reset(new std::string(launch_id));
422     AppRuntimeEventRouter::DispatchOnLaunchedEvent(context, app, source,
423                                                    std::move(launch_data));
424     return;
425   }
426
427   base::FilePath file_path(command_line.GetArgs()[0]);
428   scoped_refptr<PlatformAppPathLauncher> launcher =
429       new PlatformAppPathLauncher(context, app, file_path);
430   launcher->LaunchWithRelativePath(current_directory);
431 }
432
433 void LaunchPlatformAppWithPath(content::BrowserContext* context,
434                                const Extension* app,
435                                const base::FilePath& file_path) {
436   scoped_refptr<PlatformAppPathLauncher> launcher =
437       new PlatformAppPathLauncher(context, app, file_path);
438   launcher->Launch();
439 }
440
441 void LaunchPlatformAppWithAction(
442     content::BrowserContext* context,
443     const extensions::Extension* app,
444     std::unique_ptr<app_runtime::ActionData> action_data,
445     const base::FilePath& file_path) {
446   CHECK(!action_data || !action_data->is_lock_screen_action ||
447         !*action_data->is_lock_screen_action ||
448         app->permissions_data()->HasAPIPermission(
449             extensions::APIPermission::kLockScreen))
450       << "Launching lock screen action handler requires lockScreen permission.";
451
452   scoped_refptr<PlatformAppPathLauncher> launcher =
453       new PlatformAppPathLauncher(context, app, file_path);
454   launcher->set_action_data(std::move(action_data));
455   launcher->set_launch_source(extensions::AppLaunchSource::SOURCE_UNTRACKED);
456   launcher->Launch();
457 }
458
459 void LaunchPlatformApp(content::BrowserContext* context,
460                        const Extension* app,
461                        extensions::AppLaunchSource source) {
462   LaunchPlatformAppWithCommandLine(
463       context, app, base::CommandLine(base::CommandLine::NO_PROGRAM),
464       base::FilePath(), source);
465 }
466
467 void LaunchPlatformAppWithFileHandler(
468     content::BrowserContext* context,
469     const Extension* app,
470     const std::string& handler_id,
471     const std::vector<base::FilePath>& entry_paths) {
472   scoped_refptr<PlatformAppPathLauncher> launcher =
473       new PlatformAppPathLauncher(context, app, entry_paths);
474   launcher->LaunchWithHandler(handler_id);
475 }
476
477 void RestartPlatformApp(content::BrowserContext* context,
478                         const Extension* app) {
479   EventRouter* event_router = EventRouter::Get(context);
480   bool listening_to_restart = event_router->ExtensionHasEventListener(
481       app->id(), app_runtime::OnRestarted::kEventName);
482
483   if (listening_to_restart) {
484     AppRuntimeEventRouter::DispatchOnRestartedEvent(context, app);
485     return;
486   }
487
488   extensions::ExtensionPrefs* extension_prefs =
489       extensions::ExtensionPrefs::Get(context);
490   bool had_windows = extension_prefs->IsActive(app->id());
491   extension_prefs->SetIsActive(app->id(), false);
492   bool listening_to_launch = event_router->ExtensionHasEventListener(
493       app->id(), app_runtime::OnLaunched::kEventName);
494
495   if (listening_to_launch && had_windows) {
496     AppRuntimeEventRouter::DispatchOnLaunchedEvent(
497         context, app, extensions::SOURCE_RESTART, nullptr);
498   }
499 }
500
501 void LaunchPlatformAppWithUrl(content::BrowserContext* context,
502                               const Extension* app,
503                               const std::string& handler_id,
504                               const GURL& url,
505                               const GURL& referrer_url) {
506   AppRuntimeEventRouter::DispatchOnLaunchedEventWithUrl(
507       context, app, handler_id, url, referrer_url);
508 }
509
510 }  // namespace apps