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