1 // Copyright (c) 2012 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/extensions/api/messaging/native_process_launcher.h"
6
7 #include <inttypes.h>
8
9 #include <utility>
10
11 #include "base/base64.h"
12 #include "base/base_paths.h"
13 #include "base/base_switches.h"
14 #include "base/bind.h"
15 #include "base/callback.h"
16 #include "base/command_line.h"
17 #include "base/files/file_util.h"
18 #include "base/json/json_writer.h"
19 #include "base/logging.h"
20 #include "base/macros.h"
21 #include "base/memory/ref_counted.h"
22 #include "base/memory/scoped_refptr.h"
23 #include "base/path_service.h"
24 #include "base/strings/strcat.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/task/thread_pool.h"
27 #include "base/values.h"
28 #include "build/build_config.h"
29 #include "chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h"
30 #include "chrome/common/chrome_features.h"
31 #include "chrome/common/chrome_paths.h"
32 #include "chrome/common/chrome_switches.h"
33 #include "content/public/browser/browser_task_traits.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "url/gurl.h"
36
37 #if defined(OS_WIN)
38 #include "ui/views/win/hwnd_util.h"
39 #endif
40
41 namespace extensions {
42
43 namespace {
44
45 // Default implementation on NativeProcessLauncher interface.
46 class NativeProcessLauncherImpl : public NativeProcessLauncher {
47 public:
48 NativeProcessLauncherImpl(bool allow_user_level_hosts,
49 intptr_t native_window,
50 const base::FilePath& profile_directory,
51 bool require_native_initiated_connections,
52 const std::string& connect_id,
53 const std::string& error_arg);
54 ~NativeProcessLauncherImpl() override;
55
56 void Launch(const GURL& origin,
57 const std::string& native_host_name,
58 const LaunchedCallback& callback) const override;
59
60 private:
61 class Core : public base::RefCountedThreadSafe<Core> {
62 public:
63 Core(bool allow_user_level_hosts,
64 intptr_t native_window,
65 const base::FilePath& profile_directory,
66 bool require_native_initiated_connections,
67 const std::string& connect_id,
68 const std::string& error_arg);
69 void Launch(const GURL& origin,
70 const std::string& native_host_name,
71 const LaunchedCallback& callback);
72 void Detach();
73
74 private:
75 friend class base::RefCountedThreadSafe<Core>;
76 virtual ~Core();
77
78 void DoLaunchOnThreadPool(const GURL& origin,
79 const std::string& native_host_name,
80 const LaunchedCallback& callback);
81 void PostErrorResult(const LaunchedCallback& callback, LaunchResult error);
82 void PostResult(const LaunchedCallback& callback,
83 base::Process process,
84 base::File read_file,
85 base::File write_file);
86 void CallCallbackOnIOThread(const LaunchedCallback& callback,
87 LaunchResult result,
88 base::Process process,
89 base::File read_file,
90 base::File write_file);
91
92 bool detached_;
93
94 const bool allow_user_level_hosts_;
95
96 const base::FilePath profile_directory_;
97
98 const bool require_native_initiated_connections_;
99
100 const std::string connect_id_;
101 const std::string error_arg_;
102
103 #if defined(OS_WIN)
104 // Handle of the native window corresponding to the extension.
105 intptr_t window_handle_;
106 #endif // OS_WIN
107
108 DISALLOW_COPY_AND_ASSIGN(Core);
109 };
110
111 scoped_refptr<Core> core_;
112
113 DISALLOW_COPY_AND_ASSIGN(NativeProcessLauncherImpl);
114 };
115
Core(bool allow_user_level_hosts,intptr_t window_handle,const base::FilePath & profile_directory,bool require_native_initiated_connections,const std::string & connect_id,const std::string & error_arg)116 NativeProcessLauncherImpl::Core::Core(bool allow_user_level_hosts,
117 intptr_t window_handle,
118 const base::FilePath& profile_directory,
119 bool require_native_initiated_connections,
120 const std::string& connect_id,
121 const std::string& error_arg)
122 : detached_(false),
123 allow_user_level_hosts_(allow_user_level_hosts),
124 profile_directory_(profile_directory),
125 require_native_initiated_connections_(
126 require_native_initiated_connections),
127 connect_id_(connect_id),
128 error_arg_(error_arg)
129 #if defined(OS_WIN)
130 ,
131 window_handle_(window_handle)
132 #endif // OS_WIN
133 {}
134
~Core()135 NativeProcessLauncherImpl::Core::~Core() {
136 DCHECK(detached_);
137 }
138
Detach()139 void NativeProcessLauncherImpl::Core::Detach() {
140 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
141 detached_ = true;
142 }
143
Launch(const GURL & origin,const std::string & native_host_name,const LaunchedCallback & callback)144 void NativeProcessLauncherImpl::Core::Launch(
145 const GURL& origin,
146 const std::string& native_host_name,
147 const LaunchedCallback& callback) {
148 base::ThreadPool::PostTask(
149 FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
150 base::BindOnce(&Core::DoLaunchOnThreadPool, this, origin,
151 native_host_name, callback));
152 }
153
DoLaunchOnThreadPool(const GURL & origin,const std::string & native_host_name,const LaunchedCallback & callback)154 void NativeProcessLauncherImpl::Core::DoLaunchOnThreadPool(
155 const GURL& origin,
156 const std::string& native_host_name,
157 const LaunchedCallback& callback) {
158 if (!NativeMessagingHostManifest::IsValidName(native_host_name)) {
159 PostErrorResult(callback, RESULT_INVALID_NAME);
160 return;
161 }
162
163 std::string error_message;
164 base::FilePath manifest_path =
165 FindManifest(native_host_name, allow_user_level_hosts_, &error_message);
166
167 if (manifest_path.empty()) {
168 LOG(WARNING) << "Can't find manifest for native messaging host "
169 << native_host_name;
170 PostErrorResult(callback, RESULT_NOT_FOUND);
171 return;
172 }
173
174 std::unique_ptr<NativeMessagingHostManifest> manifest =
175 NativeMessagingHostManifest::Load(manifest_path, &error_message);
176
177 if (!manifest) {
178 LOG(WARNING) << "Failed to load manifest for native messaging host "
179 << native_host_name << ": " << error_message;
180 PostErrorResult(callback, RESULT_NOT_FOUND);
181 return;
182 }
183
184 if (manifest->name() != native_host_name) {
185 LOG(WARNING) << "Failed to load manifest for native messaging host "
186 << native_host_name
187 << ": Invalid name specified in the manifest.";
188 PostErrorResult(callback, RESULT_NOT_FOUND);
189 return;
190 }
191
192 if (!manifest->allowed_origins().MatchesSecurityOrigin(origin)) {
193 // Not an allowed origin.
194 PostErrorResult(callback, RESULT_FORBIDDEN);
195 return;
196 }
197
198 if (require_native_initiated_connections_ &&
199 !manifest->supports_native_initiated_connections()) {
200 PostErrorResult(callback, RESULT_FORBIDDEN);
201 return;
202 }
203
204 base::FilePath host_path = manifest->path();
205 if (!host_path.IsAbsolute()) {
206 // On Windows host path is allowed to be relative to the location of the
207 // manifest file. On all other platforms the path must be absolute.
208 #if defined(OS_WIN)
209 host_path = manifest_path.DirName().Append(host_path);
210 #else // defined(OS_WIN)
211 LOG(WARNING) << "Native messaging host path must be absolute for "
212 << native_host_name;
213 PostErrorResult(callback, RESULT_NOT_FOUND);
214 return;
215 #endif // !defined(OS_WIN)
216 }
217
218 // In case when the manifest file is there, but the host binary doesn't exist
219 // report the NOT_FOUND error.
220 if (!base::PathExists(host_path)) {
221 LOG(WARNING)
222 << "Found manifest, but not the binary for native messaging host "
223 << native_host_name << ". Host path specified in the manifest: "
224 << host_path.AsUTF8Unsafe();
225 PostErrorResult(callback, RESULT_NOT_FOUND);
226 return;
227 }
228
229 base::CommandLine command_line(host_path);
230 // Note: The origin must be the first argument, so do not use AppendSwitch*
231 // hereafter because CommandLine inserts these switches before the other
232 // arguments.
233 command_line.AppendArg(origin.spec());
234
235 // Pass handle of the native view window to the native messaging host. This
236 // way the host will be able to create properly focused UI windows.
237 #if defined(OS_WIN)
238 command_line.AppendArg(
239 base::StringPrintf("--parent-window=%" PRIdPTR, window_handle_));
240 #endif // !defined(OS_WIN)
241
242 bool send_connect_id = false;
243 if (!error_arg_.empty()) {
244 send_connect_id = true;
245 command_line.AppendArg(error_arg_);
246 } else if (manifest->supports_native_initiated_connections() &&
247 !profile_directory_.empty()) {
248 send_connect_id = true;
249 base::FilePath exe_path;
250 base::PathService::Get(base::FILE_EXE, &exe_path);
251
252 base::CommandLine reconnect_command_line(exe_path);
253 reconnect_command_line.AppendSwitch(::switches::kNoStartupWindow);
254 reconnect_command_line.AppendSwitchASCII(
255 ::switches::kNativeMessagingConnectHost, native_host_name);
256 reconnect_command_line.AppendSwitchASCII(
257 ::switches::kNativeMessagingConnectExtension, origin.host());
258 reconnect_command_line.AppendSwitchASCII(::switches::kEnableFeatures,
259 features::kOnConnectNative.name);
260 reconnect_command_line.AppendSwitchPath(::switches::kProfileDirectory,
261 profile_directory_.BaseName());
262 reconnect_command_line.AppendSwitchPath(::switches::kUserDataDir,
263 profile_directory_.DirName());
264 #if defined(OS_WIN)
265 reconnect_command_line.AppendArg(
266 ::switches::kPrefetchArgumentBrowserBackground);
267 #endif
268 base::Value args(base::Value::Type::LIST);
269 for (const auto& arg : reconnect_command_line.argv()) {
270 args.Append(arg);
271 }
272 std::string encoded_reconnect_command;
273 bool success =
274 base::JSONWriter::Write(std::move(args), &encoded_reconnect_command);
275 DCHECK(success);
276 base::Base64Encode(encoded_reconnect_command, &encoded_reconnect_command);
277 command_line.AppendArg(
278 base::StrCat({"--reconnect-command=", encoded_reconnect_command}));
279 }
280
281 if (send_connect_id && !connect_id_.empty()) {
282 command_line.AppendArg(base::StrCat(
283 {"--", switches::kNativeMessagingConnectId, "=", connect_id_}));
284 }
285
286 base::Process process;
287 base::File read_file;
288 base::File write_file;
289 if (NativeProcessLauncher::LaunchNativeProcess(
290 command_line, &process, &read_file, &write_file)) {
291 PostResult(callback, std::move(process), std::move(read_file),
292 std::move(write_file));
293 } else {
294 PostErrorResult(callback, RESULT_FAILED_TO_START);
295 }
296 }
297
CallCallbackOnIOThread(const LaunchedCallback & callback,LaunchResult result,base::Process process,base::File read_file,base::File write_file)298 void NativeProcessLauncherImpl::Core::CallCallbackOnIOThread(
299 const LaunchedCallback& callback,
300 LaunchResult result,
301 base::Process process,
302 base::File read_file,
303 base::File write_file) {
304 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
305 if (detached_)
306 return;
307
308 callback.Run(result, std::move(process), std::move(read_file),
309 std::move(write_file));
310 }
311
PostErrorResult(const LaunchedCallback & callback,LaunchResult error)312 void NativeProcessLauncherImpl::Core::PostErrorResult(
313 const LaunchedCallback& callback,
314 LaunchResult error) {
315 content::GetIOThreadTaskRunner({})->PostTask(
316 FROM_HERE,
317 base::BindOnce(&NativeProcessLauncherImpl::Core::CallCallbackOnIOThread,
318 this, callback, error, base::Process(), base::File(),
319 base::File()));
320 }
321
PostResult(const LaunchedCallback & callback,base::Process process,base::File read_file,base::File write_file)322 void NativeProcessLauncherImpl::Core::PostResult(
323 const LaunchedCallback& callback,
324 base::Process process,
325 base::File read_file,
326 base::File write_file) {
327 content::GetIOThreadTaskRunner({})->PostTask(
328 FROM_HERE,
329 base::BindOnce(&NativeProcessLauncherImpl::Core::CallCallbackOnIOThread,
330 this, callback, RESULT_SUCCESS, std::move(process),
331 std::move(read_file), std::move(write_file)));
332 }
333
NativeProcessLauncherImpl(bool allow_user_level_hosts,intptr_t window_handle,const base::FilePath & profile_directory,bool require_native_initiated_connections,const std::string & connect_id,const std::string & error_arg)334 NativeProcessLauncherImpl::NativeProcessLauncherImpl(
335 bool allow_user_level_hosts,
336 intptr_t window_handle,
337 const base::FilePath& profile_directory,
338 bool require_native_initiated_connections,
339 const std::string& connect_id,
340 const std::string& error_arg)
341 : core_(base::MakeRefCounted<Core>(allow_user_level_hosts,
342 window_handle,
343 profile_directory,
344 require_native_initiated_connections,
345 connect_id,
346 error_arg)) {}
347
~NativeProcessLauncherImpl()348 NativeProcessLauncherImpl::~NativeProcessLauncherImpl() {
349 core_->Detach();
350 }
351
Launch(const GURL & origin,const std::string & native_host_name,const LaunchedCallback & callback) const352 void NativeProcessLauncherImpl::Launch(const GURL& origin,
353 const std::string& native_host_name,
354 const LaunchedCallback& callback) const {
355 core_->Launch(origin, native_host_name, callback);
356 }
357
358 } // namespace
359
360 // static
CreateDefault(bool allow_user_level_hosts,gfx::NativeView native_view,const base::FilePath & profile_directory,bool require_native_initiated_connections,const std::string & connect_id,const std::string & error_arg)361 std::unique_ptr<NativeProcessLauncher> NativeProcessLauncher::CreateDefault(
362 bool allow_user_level_hosts,
363 gfx::NativeView native_view,
364 const base::FilePath& profile_directory,
365 bool require_native_initiated_connections,
366 const std::string& connect_id,
367 const std::string& error_arg) {
368 intptr_t window_handle = 0;
369 #if defined(OS_WIN)
370 window_handle = reinterpret_cast<intptr_t>(
371 views::HWNDForNativeView(native_view));
372 #endif
373 return std::make_unique<NativeProcessLauncherImpl>(
374 allow_user_level_hosts, window_handle, profile_directory,
375 require_native_initiated_connections, connect_id, error_arg);
376 }
377
378 } // namespace extensions
379