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