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 <set>
8#include <utility>
9
10#include "base/command_line.h"
11#include "base/files/file_path.h"
12#include "base/files/file_util.h"
13#include "base/logging.h"
14#include "base/memory/ref_counted.h"
15#include "base/strings/string_util.h"
16#include "base/strings/utf_string_conversions.h"
17#include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
18#include "chrome/browser/extensions/api/file_handlers/mime_util.h"
19#include "chrome/browser/extensions/api/file_system/file_system_api.h"
20#include "chrome/browser/profiles/profile.h"
21#include "content/public/browser/browser_thread.h"
22#include "content/public/browser/render_process_host.h"
23#include "content/public/browser/web_contents.h"
24#include "content/public/common/content_switches.h"
25#include "content/public/common/url_constants.h"
26#include "extensions/browser/api/app_runtime/app_runtime_api.h"
27#include "extensions/browser/event_router.h"
28#include "extensions/browser/extension_host.h"
29#include "extensions/browser/extension_prefs.h"
30#include "extensions/browser/extension_system.h"
31#include "extensions/browser/granted_file_entry.h"
32#include "extensions/browser/lazy_background_task_queue.h"
33#include "extensions/browser/process_manager.h"
34#include "extensions/common/api/app_runtime.h"
35#include "extensions/common/extension.h"
36#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
37#include "net/base/filename_util.h"
38#include "net/base/net_util.h"
39#include "url/gurl.h"
40
41#if defined(OS_CHROMEOS)
42#include "components/user_manager/user_manager.h"
43#endif
44
45namespace app_runtime = extensions::core_api::app_runtime;
46
47using content::BrowserThread;
48using extensions::AppRuntimeEventRouter;
49using extensions::app_file_handler_util::CreateFileEntry;
50using extensions::app_file_handler_util::FileHandlerCanHandleFile;
51using extensions::app_file_handler_util::FileHandlerForId;
52using extensions::app_file_handler_util::FirstFileHandlerForFile;
53using extensions::app_file_handler_util::HasFileSystemWritePermission;
54using extensions::app_file_handler_util::PrepareFilesForWritableApp;
55using extensions::EventRouter;
56using extensions::Extension;
57using extensions::ExtensionHost;
58using extensions::ExtensionSystem;
59using extensions::GrantedFileEntry;
60
61namespace apps {
62
63namespace {
64
65const char kFallbackMimeType[] = "application/octet-stream";
66
67bool DoMakePathAbsolute(const base::FilePath& current_directory,
68                        base::FilePath* file_path) {
69  DCHECK(file_path);
70  if (file_path->IsAbsolute())
71    return true;
72
73  if (current_directory.empty()) {
74    *file_path = base::MakeAbsoluteFilePath(*file_path);
75    return !file_path->empty();
76  }
77
78  if (!current_directory.IsAbsolute())
79    return false;
80
81  *file_path = current_directory.Append(*file_path);
82  return true;
83}
84
85// Helper method to launch the platform app |extension| with no data. This
86// should be called in the fallback case, where it has been impossible to
87// load or obtain file launch data.
88void LaunchPlatformAppWithNoData(Profile* profile, const Extension* extension) {
89  DCHECK_CURRENTLY_ON(BrowserThread::UI);
90  AppRuntimeEventRouter::DispatchOnLaunchedEvent(profile, extension);
91}
92
93// Class to handle launching of platform apps to open specific paths.
94// An instance of this class is created for each launch. The lifetime of these
95// instances is managed by reference counted pointers. As long as an instance
96// has outstanding tasks on a message queue it will be retained; once all
97// outstanding tasks are completed it will be deleted.
98class PlatformAppPathLauncher
99    : public base::RefCountedThreadSafe<PlatformAppPathLauncher> {
100 public:
101  PlatformAppPathLauncher(Profile* profile,
102                          const Extension* extension,
103                          const std::vector<base::FilePath>& file_paths)
104      : profile_(profile),
105        extension_(extension),
106        file_paths_(file_paths),
107        collector_(profile) {}
108
109  PlatformAppPathLauncher(Profile* profile,
110                          const Extension* extension,
111                          const base::FilePath& file_path)
112      : profile_(profile), extension_(extension), collector_(profile) {
113    if (!file_path.empty())
114      file_paths_.push_back(file_path);
115  }
116
117  void Launch() {
118    DCHECK_CURRENTLY_ON(BrowserThread::UI);
119    if (file_paths_.empty()) {
120      LaunchPlatformAppWithNoData(profile_, extension_);
121      return;
122    }
123
124    for (size_t i = 0; i < file_paths_.size(); ++i) {
125      DCHECK(file_paths_[i].IsAbsolute());
126    }
127
128    if (HasFileSystemWritePermission(extension_)) {
129      PrepareFilesForWritableApp(
130          file_paths_,
131          profile_,
132          false,
133          base::Bind(&PlatformAppPathLauncher::OnFilesValid, this),
134          base::Bind(&PlatformAppPathLauncher::OnFilesInvalid, this));
135      return;
136    }
137
138    OnFilesValid();
139  }
140
141  void LaunchWithHandler(const std::string& handler_id) {
142    handler_id_ = handler_id;
143    Launch();
144  }
145
146  void LaunchWithRelativePath(const base::FilePath& current_directory) {
147    BrowserThread::PostTask(
148        BrowserThread::FILE,
149        FROM_HERE,
150        base::Bind(&PlatformAppPathLauncher::MakePathAbsolute,
151                   this,
152                   current_directory));
153  }
154
155 private:
156  friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>;
157
158  virtual ~PlatformAppPathLauncher() {}
159
160  void MakePathAbsolute(const base::FilePath& current_directory) {
161    DCHECK_CURRENTLY_ON(BrowserThread::FILE);
162
163    for (std::vector<base::FilePath>::iterator it = file_paths_.begin();
164         it != file_paths_.end();
165         ++it) {
166      if (!DoMakePathAbsolute(current_directory, &*it)) {
167        LOG(WARNING) << "Cannot make absolute path from " << it->value();
168        BrowserThread::PostTask(
169            BrowserThread::UI,
170            FROM_HERE,
171            base::Bind(&PlatformAppPathLauncher::LaunchWithNoLaunchData, this));
172        return;
173      }
174    }
175
176    BrowserThread::PostTask(BrowserThread::UI,
177                            FROM_HERE,
178                            base::Bind(&PlatformAppPathLauncher::Launch, this));
179  }
180
181  void OnFilesValid() {
182    collector_.CollectForLocalPaths(
183        file_paths_,
184        base::Bind(&PlatformAppPathLauncher::OnMimeTypesCollected, this));
185  }
186
187  void OnFilesInvalid(const base::FilePath& /* error_path */) {
188    LaunchWithNoLaunchData();
189  }
190
191  void LaunchWithNoLaunchData() {
192    // This method is required as an entry point on the UI thread.
193    LaunchPlatformAppWithNoData(profile_, extension_);
194  }
195
196  void OnMimeTypesCollected(scoped_ptr<std::vector<std::string> > mime_types) {
197    DCHECK(file_paths_.size() == mime_types->size());
198
199    // If fetching a mime type failed, then use a fallback one.
200    for (size_t i = 0; i < mime_types->size(); ++i) {
201      const std::string mime_type =
202          !(*mime_types)[i].empty() ? (*mime_types)[i] : kFallbackMimeType;
203      mime_types_.push_back(mime_type);
204    }
205
206    // Find file handler from the platform app for the file being opened.
207    const extensions::FileHandlerInfo* handler = NULL;
208    if (!handler_id_.empty()) {
209      handler = FileHandlerForId(*extension_, handler_id_);
210      if (handler) {
211        for (size_t i = 0; i < file_paths_.size(); ++i) {
212          if (!FileHandlerCanHandleFile(
213                  *handler, mime_types_[i], file_paths_[i])) {
214            LOG(WARNING)
215                << "Extension does not provide a valid file handler for "
216                << file_paths_[i].value();
217            handler = NULL;
218            break;
219          }
220        }
221      }
222    } else {
223      std::set<std::pair<base::FilePath, std::string> > path_and_file_type_set;
224      for (size_t i = 0; i < file_paths_.size(); ++i) {
225        path_and_file_type_set.insert(
226            std::make_pair(file_paths_[i], mime_types_[i]));
227      }
228      const std::vector<const extensions::FileHandlerInfo*>& handlers =
229          extensions::app_file_handler_util::FindFileHandlersForFiles(
230              *extension_, path_and_file_type_set);
231      if (!handlers.empty())
232        handler = handlers[0];
233    }
234
235    // If this app doesn't have a file handler that supports the file, launch
236    // with no launch data.
237    if (!handler) {
238      LOG(WARNING) << "Extension does not provide a valid file handler.";
239      LaunchWithNoLaunchData();
240      return;
241    }
242
243    if (handler_id_.empty())
244      handler_id_ = handler->id;
245
246    // Access needs to be granted to the file for the process associated with
247    // the extension. To do this the ExtensionHost is needed. This might not be
248    // available, or it might be in the process of being unloaded, in which case
249    // the lazy background task queue is used to load the extension and then
250    // call back to us.
251    extensions::LazyBackgroundTaskQueue* const queue =
252        ExtensionSystem::Get(profile_)->lazy_background_task_queue();
253    if (queue->ShouldEnqueueTask(profile_, extension_)) {
254      queue->AddPendingTask(
255          profile_,
256          extension_->id(),
257          base::Bind(&PlatformAppPathLauncher::GrantAccessToFilesAndLaunch,
258                     this));
259      return;
260    }
261
262    extensions::ProcessManager* const process_manager =
263        ExtensionSystem::Get(profile_)->process_manager();
264    ExtensionHost* const host =
265        process_manager->GetBackgroundHostForExtension(extension_->id());
266    DCHECK(host);
267    GrantAccessToFilesAndLaunch(host);
268  }
269
270  void GrantAccessToFilesAndLaunch(ExtensionHost* host) {
271    // If there was an error loading the app page, |host| will be NULL.
272    if (!host) {
273      LOG(ERROR) << "Could not load app page for " << extension_->id();
274      return;
275    }
276
277    std::vector<GrantedFileEntry> file_entries;
278    for (size_t i = 0; i < file_paths_.size(); ++i) {
279      file_entries.push_back(
280          CreateFileEntry(profile_,
281                          extension_,
282                          host->render_process_host()->GetID(),
283                          file_paths_[i],
284                          false));
285    }
286
287    AppRuntimeEventRouter::DispatchOnLaunchedEventWithFileEntries(
288        profile_, extension_, handler_id_, mime_types_, file_entries);
289  }
290
291  // The profile the app should be run in.
292  Profile* profile_;
293  // The extension providing the app.
294  // TODO(benwells): Hold onto the extension ID instead of a pointer as it
295  // is possible the extension will be unloaded while we're doing our thing.
296  // See http://crbug.com/372270 for details.
297  const Extension* extension_;
298  // The path to be passed through to the app.
299  std::vector<base::FilePath> file_paths_;
300  std::vector<std::string> mime_types_;
301  // The ID of the file handler used to launch the app.
302  std::string handler_id_;
303  extensions::app_file_handler_util::MimeTypeCollector collector_;
304
305  DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher);
306};
307
308}  // namespace
309
310void LaunchPlatformAppWithCommandLine(Profile* profile,
311                                      const Extension* extension,
312                                      const CommandLine& command_line,
313                                      const base::FilePath& current_directory) {
314  // An app with "kiosk_only" should not be installed and launched
315  // outside of ChromeOS kiosk mode in the first place. This is a defensive
316  // check in case this scenario does occur.
317  if (extensions::KioskModeInfo::IsKioskOnly(extension)) {
318    bool in_kiosk_mode = false;
319#if defined(OS_CHROMEOS)
320    user_manager::UserManager* user_manager = user_manager::UserManager::Get();
321    in_kiosk_mode = user_manager && user_manager->IsLoggedInAsKioskApp();
322#endif
323    if (!in_kiosk_mode) {
324      LOG(ERROR) << "App with 'kiosk_only' attribute must be run in "
325          << " ChromeOS kiosk mode.";
326      NOTREACHED();
327      return;
328    }
329  }
330
331#if defined(OS_WIN)
332  base::CommandLine::StringType about_blank_url(
333      base::ASCIIToWide(url::kAboutBlankURL));
334#else
335  base::CommandLine::StringType about_blank_url(url::kAboutBlankURL);
336#endif
337  CommandLine::StringVector args = command_line.GetArgs();
338  // Browser tests will add about:blank to the command line. This should
339  // never be interpreted as a file to open, as doing so with an app that
340  // has write access will result in a file 'about' being created, which
341  // causes problems on the bots.
342  if (args.empty() || (command_line.HasSwitch(switches::kTestType) &&
343                       args[0] == about_blank_url)) {
344    LaunchPlatformAppWithNoData(profile, extension);
345    return;
346  }
347
348  base::FilePath file_path(command_line.GetArgs()[0]);
349  scoped_refptr<PlatformAppPathLauncher> launcher =
350      new PlatformAppPathLauncher(profile, extension, file_path);
351  launcher->LaunchWithRelativePath(current_directory);
352}
353
354void LaunchPlatformAppWithPath(Profile* profile,
355                               const Extension* extension,
356                               const base::FilePath& file_path) {
357  scoped_refptr<PlatformAppPathLauncher> launcher =
358      new PlatformAppPathLauncher(profile, extension, file_path);
359  launcher->Launch();
360}
361
362void LaunchPlatformApp(Profile* profile, const Extension* extension) {
363  LaunchPlatformAppWithCommandLine(profile,
364                                   extension,
365                                   CommandLine(CommandLine::NO_PROGRAM),
366                                   base::FilePath());
367}
368
369void LaunchPlatformAppWithFileHandler(
370    Profile* profile,
371    const Extension* extension,
372    const std::string& handler_id,
373    const std::vector<base::FilePath>& file_paths) {
374  scoped_refptr<PlatformAppPathLauncher> launcher =
375      new PlatformAppPathLauncher(profile, extension, file_paths);
376  launcher->LaunchWithHandler(handler_id);
377}
378
379void RestartPlatformApp(Profile* profile, const Extension* extension) {
380  EventRouter* event_router = EventRouter::Get(profile);
381  bool listening_to_restart = event_router->
382      ExtensionHasEventListener(extension->id(),
383                                app_runtime::OnRestarted::kEventName);
384
385  if (listening_to_restart) {
386    AppRuntimeEventRouter::DispatchOnRestartedEvent(profile, extension);
387    return;
388  }
389
390  extensions::ExtensionPrefs* extension_prefs =
391      extensions::ExtensionPrefs::Get(profile);
392  bool had_windows = extension_prefs->IsActive(extension->id());
393  extension_prefs->SetIsActive(extension->id(), false);
394  bool listening_to_launch = event_router->
395      ExtensionHasEventListener(extension->id(),
396                                app_runtime::OnLaunched::kEventName);
397
398  if (listening_to_launch && had_windows)
399    LaunchPlatformAppWithNoData(profile, extension);
400}
401
402void LaunchPlatformAppWithUrl(Profile* profile,
403                              const Extension* extension,
404                              const std::string& handler_id,
405                              const GURL& url,
406                              const GURL& referrer_url) {
407  AppRuntimeEventRouter::DispatchOnLaunchedEventWithUrl(
408      profile, extension, handler_id, url, referrer_url);
409}
410
411}  // namespace apps
412