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