blob: b0eac957f79680691d98ac04b96471d9798ce092 [file] [log] [blame]
[email protected]a82af392012-02-24 04:40:201// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]14a000d2010-04-29 21:44:242// 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/platform_util.h"
6
Ryan Gonzalezcdc98cbb2021-07-08 04:49:327#include <fcntl.h>
8
9#include <string>
10#include <vector>
11
[email protected]7c3228a2011-11-11 21:35:2212#include "base/bind.h"
Ryan Gonzalezcdc98cbb2021-07-08 04:49:3213#include "base/containers/contains.h"
Lei Zhang46a1cad2022-01-17 10:40:3114#include "base/logging.h"
Alex Clarke112c5122019-11-22 12:26:4215#include "base/no_destructor.h"
Ryan Gonzalezcdc98cbb2021-07-08 04:49:3216#include "base/posix/eintr_wrapper.h"
[email protected]d09a4ce1c2013-07-24 17:37:0217#include "base/process/kill.h"
18#include "base/process/launch.h"
maksim.sisov4e6a58a62016-05-02 19:34:4719#include "base/strings/string_util.h"
[email protected]e309f312013-06-07 21:50:0820#include "base/strings/utf_string_conversions.h"
Gabriel Charette40182e62019-07-25 19:36:2621#include "base/threading/scoped_blocking_call.h"
Kai Uwe Broulikda1cd322019-11-18 21:55:0622#include "chrome/browser/chrome_notification_types.h"
asanka655d1112015-03-07 05:33:4123#include "chrome/browser/platform_util_internal.h"
Colin Blundell980e85d22021-06-14 16:25:1124// This file gets pulled in in Chromecast builds, which causes "gn check" to
25// complain as Chromecast doesn't use (or depend on) //components/dbus.
26// TODO(crbug.com/1215474): Eliminate //chrome being visible in the GN structure
27// on Chromecast and remove the nogncheck below.
28#include "components/dbus/thread_linux/dbus_thread_linux.h" // nogncheck
[email protected]7c3228a2011-11-11 21:35:2229#include "content/public/browser/browser_thread.h"
Kai Uwe Broulikda1cd322019-11-18 21:55:0630#include "content/public/browser/notification_observer.h"
31#include "content/public/browser/notification_registrar.h"
32#include "content/public/browser/notification_service.h"
33#include "dbus/bus.h"
34#include "dbus/message.h"
35#include "dbus/object_proxy.h"
Ryan Gonzalezcdc98cbb2021-07-08 04:49:3236#include "third_party/abseil-cpp/absl/types/optional.h"
[email protected]761fa4702013-07-02 15:25:1537#include "url/gurl.h"
[email protected]14a000d2010-04-29 21:44:2438
[email protected]7c3228a2011-11-11 21:35:2239using content::BrowserThread;
40
asanka655d1112015-03-07 05:33:4141namespace platform_util {
42
[email protected]14a000d2010-04-29 21:44:2443namespace {
44
Ryan Gonzalezcdc98cbb2021-07-08 04:49:3245const char kMethodListActivatableNames[] = "ListActivatableNames";
46const char kMethodNameHasOwner[] = "NameHasOwner";
47
Kai Uwe Broulikda1cd322019-11-18 21:55:0648const char kFreedesktopFileManagerName[] = "org.freedesktop.FileManager1";
49const char kFreedesktopFileManagerPath[] = "/org/freedesktop/FileManager1";
50
51const char kMethodShowItems[] = "ShowItems";
52
Ryan Gonzalezcdc98cbb2021-07-08 04:49:3253const char kFreedesktopPortalName[] = "org.freedesktop.portal.Desktop";
54const char kFreedesktopPortalPath[] = "/org/freedesktop/portal/desktop";
55const char kFreedesktopPortalOpenURI[] = "org.freedesktop.portal.OpenURI";
56
57const char kMethodOpenDirectory[] = "OpenDirectory";
58
Kai Uwe Broulikda1cd322019-11-18 21:55:0659class ShowItemHelper : public content::NotificationObserver {
60 public:
61 static ShowItemHelper& GetInstance() {
62 static base::NoDestructor<ShowItemHelper> instance;
63 return *instance;
64 }
65
66 ShowItemHelper() {
67 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
68 content::NotificationService::AllSources());
69 }
70
71 ShowItemHelper(const ShowItemHelper&) = delete;
72 ShowItemHelper& operator=(const ShowItemHelper&) = delete;
73
74 void Observe(int type,
75 const content::NotificationSource& source,
76 const content::NotificationDetails& details) override {
77 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
78 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
79 // The browser process is about to exit. Clean up while we still can.
80 if (bus_)
81 bus_->ShutdownOnDBusThreadAndBlock();
82 bus_.reset();
Ryan Gonzalezcdc98cbb2021-07-08 04:49:3283 object_proxy_ = nullptr;
Kai Uwe Broulikda1cd322019-11-18 21:55:0684 }
85
86 void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
87 if (!bus_) {
88 // Sets up the D-Bus connection.
89 dbus::Bus::Options bus_options;
90 bus_options.bus_type = dbus::Bus::SESSION;
91 bus_options.connection_type = dbus::Bus::PRIVATE;
92 bus_options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
93 bus_ = base::MakeRefCounted<dbus::Bus>(bus_options);
94 }
95
Ryan Gonzalezcdc98cbb2021-07-08 04:49:3296 if (!dbus_proxy_) {
97 dbus_proxy_ = bus_->GetObjectProxy(DBUS_SERVICE_DBUS,
98 dbus::ObjectPath(DBUS_PATH_DBUS));
99 }
100
101 if (prefer_filemanager_interface_.has_value()) {
102 if (prefer_filemanager_interface_.value()) {
103 VLOG(1) << "Using FileManager1 to show folder";
104 ShowItemUsingFileManager(profile, full_path);
105 } else {
106 VLOG(1) << "Using OpenURI to show folder";
107 ShowItemUsingFreedesktopPortal(profile, full_path);
108 }
109 } else {
110 CheckFileManagerRunning(profile, full_path);
111 }
112 }
113
114 private:
115 void CheckFileManagerRunning(Profile* profile,
116 const base::FilePath& full_path) {
117 dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, kMethodNameHasOwner);
118 dbus::MessageWriter writer(&method_call);
119 writer.AppendString(kFreedesktopFileManagerName);
120
121 dbus_proxy_->CallMethod(
122 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
123 base::BindOnce(&ShowItemHelper::CheckFileManagerRunningResponse,
124 weak_ptr_factory_.GetWeakPtr(), profile, full_path));
125 }
126
127 void CheckFileManagerRunningResponse(Profile* profile,
128 const base::FilePath& full_path,
129 dbus::Response* response) {
130 if (prefer_filemanager_interface_.has_value()) {
131 ShowItemInFolder(profile, full_path);
132 return;
133 }
134
135 bool is_running = false;
136
137 if (!response) {
138 LOG(ERROR) << "Failed to call " << kMethodNameHasOwner;
139 } else {
140 dbus::MessageReader reader(response);
141 bool owned = false;
142
143 if (!reader.PopBool(&owned)) {
144 LOG(ERROR) << "Failed to read " << kMethodNameHasOwner << " resposne";
145 } else if (owned) {
146 is_running = true;
147 }
148 }
149
150 if (is_running) {
151 prefer_filemanager_interface_ = true;
152 ShowItemInFolder(profile, full_path);
153 } else {
154 CheckFileManagerActivatable(profile, full_path);
155 }
156 }
157
158 void CheckFileManagerActivatable(Profile* profile,
159 const base::FilePath& full_path) {
160 dbus::MethodCall method_call(DBUS_INTERFACE_DBUS,
161 kMethodListActivatableNames);
162 dbus_proxy_->CallMethod(
163 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
164 base::BindOnce(&ShowItemHelper::CheckFileManagerActivatableResponse,
165 weak_ptr_factory_.GetWeakPtr(), profile, full_path));
166 }
167
168 void CheckFileManagerActivatableResponse(Profile* profile,
169 const base::FilePath& full_path,
170 dbus::Response* response) {
171 if (prefer_filemanager_interface_.has_value()) {
172 ShowItemInFolder(profile, full_path);
173 return;
174 }
175
176 bool is_activatable = false;
177
178 if (!response) {
179 LOG(ERROR) << "Failed to call " << kMethodListActivatableNames;
180 } else {
181 dbus::MessageReader reader(response);
182 std::vector<std::string> names;
183 if (!reader.PopArrayOfStrings(&names)) {
184 LOG(ERROR) << "Failed to read " << kMethodListActivatableNames
185 << " response";
186 } else if (base::Contains(names, kFreedesktopFileManagerName)) {
187 is_activatable = true;
188 }
189 }
190
191 prefer_filemanager_interface_ = is_activatable;
192 ShowItemInFolder(profile, full_path);
193 }
194
195 void ShowItemUsingFreedesktopPortal(Profile* profile,
196 const base::FilePath& full_path) {
197 if (!object_proxy_) {
198 object_proxy_ = bus_->GetObjectProxy(
199 kFreedesktopPortalName, dbus::ObjectPath(kFreedesktopPortalPath));
200 }
201
202 base::ScopedFD fd(
203 HANDLE_EINTR(open(full_path.value().c_str(), O_RDONLY | O_CLOEXEC)));
204 if (!fd.is_valid()) {
205 PLOG(ERROR) << "Failed to open " << full_path << " for URI portal";
206
207 // At least open the parent folder, as long as we're not in the unit
208 // tests.
209 if (internal::AreShellOperationsAllowed()) {
210 OpenItem(profile, full_path.DirName(), OPEN_FOLDER,
211 OpenOperationCallback());
212 }
213
214 return;
215 }
216
217 dbus::MethodCall open_directory_call(kFreedesktopPortalOpenURI,
218 kMethodOpenDirectory);
219 dbus::MessageWriter writer(&open_directory_call);
220
221 writer.AppendString("");
222
223 // Note that AppendFileDescriptor() duplicates the fd, so we shouldn't
224 // release ownership of it here.
225 writer.AppendFileDescriptor(fd.get());
226
227 dbus::MessageWriter options_writer(nullptr);
228 writer.OpenArray("{sv}", &options_writer);
229 writer.CloseContainer(&options_writer);
230
231 ShowItemUsingBusCall(&open_directory_call, profile, full_path);
232 }
233
234 void ShowItemUsingFileManager(Profile* profile,
235 const base::FilePath& full_path) {
236 if (!object_proxy_) {
237 object_proxy_ =
Kai Uwe Broulikda1cd322019-11-18 21:55:06238 bus_->GetObjectProxy(kFreedesktopFileManagerName,
239 dbus::ObjectPath(kFreedesktopFileManagerPath));
240 }
241
242 dbus::MethodCall show_items_call(kFreedesktopFileManagerName,
243 kMethodShowItems);
244 dbus::MessageWriter writer(&show_items_call);
245
246 writer.AppendArrayOfStrings(
247 {"file://" + full_path.value()}); // List of file(s) to highlight.
248 writer.AppendString({}); // startup-id
249
Ryan Gonzalezcdc98cbb2021-07-08 04:49:32250 ShowItemUsingBusCall(&show_items_call, profile, full_path);
251 }
252
253 void ShowItemUsingBusCall(dbus::MethodCall* call,
254 Profile* profile,
255 const base::FilePath& full_path) {
Jesse McKenna14bd4f1a2020-08-10 23:54:29256 // Skip opening the folder during browser tests, to avoid leaving an open
257 // file explorer window behind.
258 if (!internal::AreShellOperationsAllowed())
259 return;
260
Ryan Gonzalezcdc98cbb2021-07-08 04:49:32261 object_proxy_->CallMethod(
262 call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
Kai Uwe Broulikda1cd322019-11-18 21:55:06263 base::BindOnce(&ShowItemHelper::ShowItemInFolderResponse,
Ryan Gonzalezcdc98cbb2021-07-08 04:49:32264 weak_ptr_factory_.GetWeakPtr(), profile, full_path,
265 call->GetMember()));
Kai Uwe Broulikda1cd322019-11-18 21:55:06266 }
267
Kai Uwe Broulikda1cd322019-11-18 21:55:06268 void ShowItemInFolderResponse(Profile* profile,
269 const base::FilePath& full_path,
Ryan Gonzalezcdc98cbb2021-07-08 04:49:32270 const std::string& method,
Kai Uwe Broulikda1cd322019-11-18 21:55:06271 dbus::Response* response) {
272 if (response)
273 return;
274
Ryan Gonzalezcdc98cbb2021-07-08 04:49:32275 LOG(ERROR) << "Error calling " << method;
276 // If the bus call fails, at least open the parent folder.
Kai Uwe Broulikda1cd322019-11-18 21:55:06277 OpenItem(profile, full_path.DirName(), OPEN_FOLDER,
278 OpenOperationCallback());
279 }
280
281 content::NotificationRegistrar registrar_;
282
283 scoped_refptr<dbus::Bus> bus_;
Ryan Gonzalezcdc98cbb2021-07-08 04:49:32284 dbus::ObjectProxy* dbus_proxy_ = nullptr;
285 dbus::ObjectProxy* object_proxy_ = nullptr;
286
287 absl::optional<bool> prefer_filemanager_interface_;
Kai Uwe Broulikda1cd322019-11-18 21:55:06288
289 base::WeakPtrFactory<ShowItemHelper> weak_ptr_factory_{this};
290};
maksim.sisov4e6a58a62016-05-02 19:34:47291
292void RunCommand(const std::string& command,
293 const base::FilePath& working_directory,
294 const std::string& arg) {
[email protected]14a000d2010-04-29 21:44:24295 std::vector<std::string> argv;
maksim.sisov4e6a58a62016-05-02 19:34:47296 argv.push_back(command);
[email protected]e9ce67a12010-11-11 20:58:49297 argv.push_back(arg);
[email protected]14a000d2010-04-29 21:44:24298
[email protected]b345c482013-08-30 18:00:39299 base::LaunchOptions options;
asanka655d1112015-03-07 05:33:41300 options.current_directory = working_directory;
[email protected]720397c52014-04-22 14:06:12301 options.allow_new_privs = true;
[email protected]14a000d2010-04-29 21:44:24302 // xdg-open can fall back on mailcap which eventually might plumb through
303 // to a command that needs a terminal. Set the environment variable telling
304 // it that we definitely don't have a terminal available and that it should
305 // bring up a new terminal if necessary. See "man mailcap".
David Benjamin76ee79eb2019-03-15 17:02:09306 options.environment["MM_NOTTTY"] = "1";
[email protected]14a000d2010-04-29 21:44:24307
308 // In Google Chrome, we do not let GNOME's bug-buddy intercept our crashes.
309 // However, we do not want this environment variable to propagate to external
310 // applications. See http://crbug.com/24120
311 char* disable_gnome_bug_buddy = getenv("GNOME_DISABLE_CRASH_DIALOG");
312 if (disable_gnome_bug_buddy &&
[email protected]b345c482013-08-30 18:00:39313 disable_gnome_bug_buddy == std::string("SET_BY_GOOGLE_CHROME"))
David Benjamin76ee79eb2019-03-15 17:02:09314 options.environment["GNOME_DISABLE_CRASH_DIALOG"] = std::string();
[email protected]14a000d2010-04-29 21:44:24315
rvargasc7b523fa2015-01-14 01:13:23316 base::Process process = base::LaunchProcess(argv, options);
317 if (process.IsValid())
Wezc18a57c2018-04-02 20:20:14318 base::EnsureProcessGetsReaped(std::move(process));
[email protected]14a000d2010-04-29 21:44:24319}
320
asanka655d1112015-03-07 05:33:41321void XDGOpen(const base::FilePath& working_directory, const std::string& path) {
maksim.sisov4e6a58a62016-05-02 19:34:47322 RunCommand("xdg-open", working_directory, path);
[email protected]e9ce67a12010-11-11 20:58:49323}
324
325void XDGEmail(const std::string& email) {
maksim.sisov4e6a58a62016-05-02 19:34:47326 RunCommand("xdg-email", base::FilePath(), email);
327}
328
[email protected]7c3228a2011-11-11 21:35:22329} // namespace
330
asanka655d1112015-03-07 05:33:41331namespace internal {
332
333void PlatformOpenVerifiedItem(const base::FilePath& path, OpenItemType type) {
Gabriel Charette40182e62019-07-25 19:36:26334 // May result in an interactive dialog.
335 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
336 base::BlockingType::MAY_BLOCK);
asanka655d1112015-03-07 05:33:41337 switch (type) {
338 case OPEN_FILE:
339 XDGOpen(path.DirName(), path.value());
340 break;
341 case OPEN_FOLDER:
342 // The utility process checks the working directory prior to the
343 // invocation of xdg-open by changing the current directory into it. This
344 // operation only succeeds if |path| is a directory. Opening "." from
345 // there ensures that the target of the operation is a directory. Note
346 // that there remains a TOCTOU race where the directory could be unlinked
347 // between the time the utility process changes into the directory and the
348 // time the application invoked by xdg-open inspects the path by name.
349 XDGOpen(path, ".");
350 break;
351 }
352}
maksim.sisov4e6a58a62016-05-02 19:34:47353
asanka655d1112015-03-07 05:33:41354} // namespace internal
[email protected]7c3228a2011-11-11 21:35:22355
[email protected]4d225de12013-12-13 09:29:17356void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
thestig00844cea2015-09-08 21:44:52357 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Kai Uwe Broulikda1cd322019-11-18 21:55:06358 ShowItemHelper::GetInstance().ShowItemInFolder(profile, full_path);
[email protected]14a000d2010-04-29 21:44:24359}
360
[email protected]7f0a3efa2013-12-12 17:16:12361void OpenExternal(Profile* profile, const GURL& url) {
thestig00844cea2015-09-08 21:44:52362 DCHECK_CURRENTLY_ON(BrowserThread::UI);
[email protected]e9ce67a12010-11-11 20:58:49363 if (url.SchemeIs("mailto"))
364 XDGEmail(url.spec());
365 else
asanka655d1112015-03-07 05:33:41366 XDGOpen(base::FilePath(), url.spec());
[email protected]14a000d2010-04-29 21:44:24367}
368
369} // namespace platform_util