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