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 "chrome/browser/chromeos/file_manager/open_with_browser.h"
6 
7 #include <stddef.h>
8 
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/logging.h"
12 #include "base/no_destructor.h"
13 #include "base/path_service.h"
14 #include "base/stl_util.h"
15 #include "base/task/post_task.h"
16 #include "base/task/thread_pool.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
19 #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h"
20 #include "chrome/browser/chromeos/fileapi/external_file_url_util.h"
21 #include "chrome/browser/plugins/plugin_prefs.h"
22 #include "chrome/browser/profiles/profile_manager.h"
23 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_tabstrip.h"
26 #include "chrome/browser/ui/browser_window.h"
27 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
28 #include "chrome/common/chrome_content_client.h"
29 #include "chrome/common/chrome_paths.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chromeos/components/drivefs/drivefs_util.h"
32 #include "chromeos/components/drivefs/mojom/drivefs.mojom.h"
33 #include "components/drive/drive_api_util.h"
34 #include "components/drive/file_system_core_util.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/plugin_service.h"
37 #include "content/public/common/pepper_plugin_info.h"
38 #include "net/base/filename_util.h"
39 
40 using content::BrowserThread;
41 using content::PluginService;
42 
43 namespace file_manager {
44 namespace util {
45 namespace {
46 
47 const base::FilePath::CharType kPdfExtension[] = FILE_PATH_LITERAL(".pdf");
48 
49 // List of file extensions viewable in the browser.
50 constexpr const base::FilePath::CharType* kFileExtensionsViewableInBrowser[] = {
51     FILE_PATH_LITERAL(".bmp"),   FILE_PATH_LITERAL(".ico"),
52     FILE_PATH_LITERAL(".jpg"),   FILE_PATH_LITERAL(".jpeg"),
53     FILE_PATH_LITERAL(".png"),   FILE_PATH_LITERAL(".webp"),
54     FILE_PATH_LITERAL(".gif"),   FILE_PATH_LITERAL(".txt"),
55     FILE_PATH_LITERAL(".html"),  FILE_PATH_LITERAL(".htm"),
56     FILE_PATH_LITERAL(".mhtml"), FILE_PATH_LITERAL(".mht"),
57     FILE_PATH_LITERAL(".xhtml"), FILE_PATH_LITERAL(".xht"),
58     FILE_PATH_LITERAL(".shtml"), FILE_PATH_LITERAL(".svg"),
59 };
60 
61 // Returns true if |file_path| is viewable in the browser (ex. HTML file).
IsViewableInBrowser(const base::FilePath & file_path)62 bool IsViewableInBrowser(const base::FilePath& file_path) {
63   for (size_t i = 0; i < base::size(kFileExtensionsViewableInBrowser); i++) {
64     if (file_path.MatchesExtension(kFileExtensionsViewableInBrowser[i]))
65       return true;
66   }
67   return false;
68 }
69 
IsPepperPluginEnabled(Profile * profile,const base::FilePath & plugin_path)70 bool IsPepperPluginEnabled(Profile* profile,
71                            const base::FilePath& plugin_path) {
72   DCHECK(profile);
73 
74   const content::PepperPluginInfo* pepper_info =
75       PluginService::GetInstance()->GetRegisteredPpapiPluginInfo(plugin_path);
76   if (!pepper_info)
77     return false;
78 
79   scoped_refptr<PluginPrefs> plugin_prefs = PluginPrefs::GetForProfile(profile);
80   if (!plugin_prefs.get())
81     return false;
82 
83   return plugin_prefs->IsPluginEnabled(pepper_info->ToWebPluginInfo());
84 }
85 
IsPdfPluginEnabled(Profile * profile)86 bool IsPdfPluginEnabled(Profile* profile) {
87   DCHECK(profile);
88 
89   static const base::NoDestructor<base::FilePath> plugin_path(
90       ChromeContentClient::kPDFPluginPath);
91   return IsPepperPluginEnabled(profile, *plugin_path);
92 }
93 
OpenNewTab(Profile * profile,const GURL & url)94 void OpenNewTab(Profile* profile, const GURL& url) {
95   DCHECK_CURRENTLY_ON(BrowserThread::UI);
96 
97   // Check the validity of the pointer so that the closure from
98   // base::BindOnce(&OpenNewTab, profile) can be passed between threads.
99   if (!g_browser_process->profile_manager()->IsValidProfile(profile))
100     return;
101 
102   chrome::ScopedTabbedBrowserDisplayer displayer(profile);
103   chrome::AddSelectedTabWithURL(displayer.browser(), url,
104       ui::PAGE_TRANSITION_LINK);
105 
106   // Since the ScopedTabbedBrowserDisplayer does not guarantee that the
107   // browser will be shown on the active desktop, we ensure the visibility.
108   multi_user_util::MoveWindowToCurrentDesktop(
109       displayer.browser()->window()->GetNativeWindow());
110 }
111 
112 // Reads the alternate URL from a GDoc file. When it fails, returns a file URL
113 // for |file_path| as fallback.
114 // Note that an alternate url is a URL to open a hosted document.
ReadUrlFromGDocAsync(const base::FilePath & file_path)115 GURL ReadUrlFromGDocAsync(const base::FilePath& file_path) {
116   GURL url = drive::util::ReadUrlFromGDocFile(file_path);
117   if (url.is_empty())
118     url = net::FilePathToFileURL(file_path);
119   return url;
120 }
121 
122 // Parse a local file to extract the Docs url and open this url.
OpenGDocUrlFromFile(const base::FilePath & file_path,Profile * profile)123 void OpenGDocUrlFromFile(const base::FilePath& file_path, Profile* profile) {
124   base::ThreadPool::PostTaskAndReplyWithResult(
125       FROM_HERE, {base::MayBlock()},
126       base::BindOnce(&ReadUrlFromGDocAsync, file_path),
127       base::BindOnce(&OpenNewTab, profile));
128 }
129 
130 // Open a hosted GDoc, from a path hosted in DriveFS.
OpenHostedDriveFsFile(const base::FilePath & file_path,Profile * profile,drive::FileError error,drivefs::mojom::FileMetadataPtr metadata)131 void OpenHostedDriveFsFile(const base::FilePath& file_path,
132                            Profile* profile,
133                            drive::FileError error,
134                            drivefs::mojom::FileMetadataPtr metadata) {
135   if (error != drive::FILE_ERROR_OK)
136     return;
137   if (drivefs::IsLocal(metadata->type)) {
138     OpenGDocUrlFromFile(file_path, profile);
139     return;
140   }
141   GURL hosted_url(metadata->alternate_url);
142   if (!hosted_url.is_valid())
143     return;
144 
145   OpenNewTab(profile, hosted_url);
146 }
147 
148 }  // namespace
149 
OpenFileWithBrowser(Profile * profile,const storage::FileSystemURL & file_system_url,const std::string & action_id)150 bool OpenFileWithBrowser(Profile* profile,
151                          const storage::FileSystemURL& file_system_url,
152                          const std::string& action_id) {
153   DCHECK_CURRENTLY_ON(BrowserThread::UI);
154   DCHECK(profile);
155 
156   const base::FilePath file_path = file_system_url.path();
157 
158   // For things supported natively by the browser, we should open it
159   // in a tab.
160   if (IsViewableInBrowser(file_path) ||
161       ShouldBeOpenedWithPlugin(profile, file_path.Extension(), action_id) ||
162       (action_id == "view-in-browser" && file_path.Extension() == "")) {
163     // Use external file URL if it is provided for the file system.
164     GURL page_url = chromeos::FileSystemURLToExternalFileURL(file_system_url);
165     if (page_url.is_empty())
166       page_url = net::FilePathToFileURL(file_path);
167 
168     OpenNewTab(profile, page_url);
169     return true;
170   }
171 
172   if (drive::util::HasHostedDocumentExtension(file_path)) {
173     if (file_manager::util::IsUnderNonNativeLocalPath(profile, file_path)) {
174       // The file is on a non-native volume. Use external file URL. If the file
175       // is on the drive volume, ExternalFileURLRequestJob redirects the URL to
176       // drive's web interface. Otherwise (e.g. MTP, FSP), the file is just
177       // downloaded in a browser tab.
178       const GURL url =
179           chromeos::FileSystemURLToExternalFileURL(file_system_url);
180       DCHECK(!url.is_empty());
181       OpenNewTab(profile, url);
182     } else {
183       drive::DriveIntegrationService* integration_service =
184           drive::DriveIntegrationServiceFactory::FindForProfile(profile);
185       base::FilePath path;
186       if (integration_service && integration_service->IsMounted() &&
187           integration_service->GetDriveFsInterface() &&
188           integration_service->GetRelativeDrivePath(file_path, &path)) {
189         integration_service->GetDriveFsInterface()->GetMetadata(
190             path, base::BindOnce(&OpenHostedDriveFsFile, file_path, profile));
191         return true;
192       }
193       OpenGDocUrlFromFile(file_path, profile);
194     }
195     return true;
196   }
197 
198   // Failed to open the file of unknown type.
199   LOG(WARNING) << "Unknown file type: " << file_path.value();
200   return false;
201 }
202 
203 // If a bundled plugin is enabled, we should open pdf/swf files in a tab.
ShouldBeOpenedWithPlugin(Profile * profile,const base::FilePath::StringType & file_extension,const std::string & action_id)204 bool ShouldBeOpenedWithPlugin(Profile* profile,
205                               const base::FilePath::StringType& file_extension,
206                               const std::string& action_id) {
207   DCHECK(profile);
208 
209   const base::FilePath file_path =
210       base::FilePath::FromUTF8Unsafe("dummy").AddExtension(file_extension);
211   if (file_path.MatchesExtension(kPdfExtension) || action_id == "view-pdf")
212     return IsPdfPluginEnabled(profile);
213   return false;
214 }
215 
216 }  // namespace util
217 }  // namespace file_manager
218