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 "base/basictypes.h"
8#include "base/bind.h"
9#include "base/callback.h"
10#include "base/command_line.h"
11#include "base/logging.h"
12#include "base/memory/ref_counted.h"
13#include "base/path_service.h"
14#include "base/strings/string_number_conversions.h"
15#include "base/strings/string_split.h"
16#include "base/threading/sequenced_worker_pool.h"
17#include "chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h"
18#include "chrome/common/chrome_paths.h"
19#include "chrome/common/chrome_switches.h"
20#include "content/public/browser/browser_thread.h"
21#include "url/gurl.h"
22
23namespace extensions {
24
25namespace {
26
27// Name of the command line switch used to pass handle of the native view to
28// the native messaging host.
29const char kParentWindowSwitchName[] = "parent-window";
30
31base::FilePath GetHostManifestPathFromCommandLine(
32    const std::string& native_host_name) {
33  const std::string& value =
34      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
35          switches::kNativeMessagingHosts);
36  if (value.empty())
37    return base::FilePath();
38
39  std::vector<std::string> hosts;
40  base::SplitString(value, ',', &hosts);
41  for (size_t i = 0; i < hosts.size(); ++i) {
42    std::vector<std::string> key_and_value;
43    base::SplitString(hosts[i], '=', &key_and_value);
44    if (key_and_value.size() != 2)
45      continue;
46    if (key_and_value[0] == native_host_name)
47      return base::FilePath::FromUTF8Unsafe(key_and_value[1]);
48  }
49
50  return base::FilePath();
51}
52
53
54// Default implementation on NativeProcessLauncher interface.
55class NativeProcessLauncherImpl : public NativeProcessLauncher {
56 public:
57  explicit NativeProcessLauncherImpl(gfx::NativeView native_view);
58  virtual ~NativeProcessLauncherImpl();
59
60  virtual void Launch(const GURL& origin,
61                      const std::string& native_host_name,
62                      LaunchedCallback callback) const OVERRIDE;
63
64 private:
65  class Core : public base::RefCountedThreadSafe<Core> {
66   public:
67    explicit Core(gfx::NativeView native_view);
68    void Launch(const GURL& origin,
69                const std::string& native_host_name,
70                LaunchedCallback callback);
71    void Detach();
72
73   private:
74    friend class base::RefCountedThreadSafe<Core>;
75    virtual ~Core();
76
77    void DoLaunchOnThreadPool(const GURL& origin,
78                              const std::string& native_host_name,
79                              LaunchedCallback callback);
80    void PostErrorResult(const LaunchedCallback& callback, LaunchResult error);
81    void PostResult(const LaunchedCallback& callback,
82                    base::PlatformFile read_file,
83                    base::PlatformFile write_file);
84    void CallCallbackOnIOThread(LaunchedCallback callback,
85                                LaunchResult result,
86                                base::PlatformFile read_file,
87                                base::PlatformFile write_file);
88
89    bool detached_;
90
91    // Handle of the native view corrsponding to the extension.
92    gfx::NativeView native_view_;
93
94    DISALLOW_COPY_AND_ASSIGN(Core);
95  };
96
97  scoped_refptr<Core> core_;
98
99  DISALLOW_COPY_AND_ASSIGN(NativeProcessLauncherImpl);
100};
101
102NativeProcessLauncherImpl::Core::Core(gfx::NativeView native_view)
103    : detached_(false),
104      native_view_(native_view) {
105}
106
107NativeProcessLauncherImpl::Core::~Core() {
108  DCHECK(detached_);
109}
110
111void NativeProcessLauncherImpl::Core::Detach() {
112  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
113  detached_ = true;
114}
115
116void NativeProcessLauncherImpl::Core::Launch(
117    const GURL& origin,
118    const std::string& native_host_name,
119    LaunchedCallback callback) {
120  content::BrowserThread::PostBlockingPoolTask(
121      FROM_HERE, base::Bind(&Core::DoLaunchOnThreadPool, this,
122                            origin, native_host_name, callback));
123}
124
125void NativeProcessLauncherImpl::Core::DoLaunchOnThreadPool(
126    const GURL& origin,
127    const std::string& native_host_name,
128    LaunchedCallback callback) {
129  DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
130
131  if (!NativeMessagingHostManifest::IsValidName(native_host_name)) {
132    PostErrorResult(callback, RESULT_INVALID_NAME);
133    return;
134  }
135
136  std::string error_message;
137  scoped_ptr<NativeMessagingHostManifest> manifest;
138
139  // First check if the manifest location is specified in the command line.
140  base::FilePath manifest_path =
141      GetHostManifestPathFromCommandLine(native_host_name);
142  if (manifest_path.empty())
143    manifest_path = FindManifest(native_host_name, &error_message);
144
145  if (manifest_path.empty()) {
146    LOG(ERROR) << "Can't find manifest for native messaging host "
147               << native_host_name;
148    PostErrorResult(callback, RESULT_NOT_FOUND);
149    return;
150  }
151
152  manifest = NativeMessagingHostManifest::Load(manifest_path, &error_message);
153
154  if (!manifest) {
155    LOG(ERROR) << "Failed to load manifest for native messaging host "
156               << native_host_name << ": " << error_message;
157    PostErrorResult(callback, RESULT_NOT_FOUND);
158    return;
159  }
160
161  if (manifest->name() != native_host_name) {
162    LOG(ERROR) << "Failed to load manifest for native messaging host "
163               << native_host_name
164               << ": Invalid name specified in the manifest.";
165    PostErrorResult(callback, RESULT_NOT_FOUND);
166    return;
167  }
168
169  if (!manifest->allowed_origins().MatchesSecurityOrigin(origin)) {
170    // Not an allowed origin.
171    PostErrorResult(callback, RESULT_FORBIDDEN);
172    return;
173  }
174
175  base::FilePath host_path = manifest->path();
176  if (!host_path.IsAbsolute()) {
177    // On Windows host path is allowed to be relative to the location of the
178    // manifest file. On all other platforms the path must be absolute.
179#if defined(OS_WIN)
180    host_path = manifest_path.DirName().Append(host_path);
181#else  // defined(OS_WIN)
182    LOG(ERROR) << "Native messaging host path must be absolute for "
183               << native_host_name;
184    PostErrorResult(callback, RESULT_NOT_FOUND);
185    return;
186#endif  // !defined(OS_WIN)
187  }
188
189  CommandLine command_line(host_path);
190  command_line.AppendArg(origin.spec());
191
192  // Pass handle of the native view window to the native messaging host. This
193  // way the host will be able to create properly focused UI windows.
194#if defined(OS_WIN)
195  int64 window_handle = reinterpret_cast<intptr_t>(native_view_);
196  command_line.AppendSwitchASCII(kParentWindowSwitchName,
197                                 base::Int64ToString(window_handle));
198#endif  // !defined(OS_WIN)
199
200  base::PlatformFile read_file;
201  base::PlatformFile write_file;
202  if (NativeProcessLauncher::LaunchNativeProcess(
203          command_line, &read_file, &write_file)) {
204    PostResult(callback, read_file, write_file);
205  } else {
206    PostErrorResult(callback, RESULT_FAILED_TO_START);
207  }
208}
209
210void NativeProcessLauncherImpl::Core::CallCallbackOnIOThread(
211    LaunchedCallback callback,
212    LaunchResult result,
213    base::PlatformFile read_file,
214    base::PlatformFile write_file) {
215  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
216  if (detached_) {
217    if (read_file != base::kInvalidPlatformFileValue)
218      base::ClosePlatformFile(read_file);
219    if (write_file != base::kInvalidPlatformFileValue)
220      base::ClosePlatformFile(write_file);
221    return;
222  }
223
224  callback.Run(result, read_file, write_file);
225}
226
227void NativeProcessLauncherImpl::Core::PostErrorResult(
228    const LaunchedCallback& callback,
229    LaunchResult error) {
230  content::BrowserThread::PostTask(
231      content::BrowserThread::IO, FROM_HERE,
232      base::Bind(&NativeProcessLauncherImpl::Core::CallCallbackOnIOThread,
233                 this, callback, error,
234                 base::kInvalidPlatformFileValue,
235                 base::kInvalidPlatformFileValue));
236}
237
238void NativeProcessLauncherImpl::Core::PostResult(
239    const LaunchedCallback& callback,
240    base::PlatformFile read_file,
241    base::PlatformFile write_file) {
242  content::BrowserThread::PostTask(
243      content::BrowserThread::IO, FROM_HERE,
244      base::Bind(&NativeProcessLauncherImpl::Core::CallCallbackOnIOThread,
245                 this, callback, RESULT_SUCCESS, read_file, write_file));
246}
247
248NativeProcessLauncherImpl::NativeProcessLauncherImpl(
249    gfx::NativeView native_view)
250  : core_(new Core(native_view)) {
251}
252
253NativeProcessLauncherImpl::~NativeProcessLauncherImpl() {
254  core_->Detach();
255}
256
257void NativeProcessLauncherImpl::Launch(const GURL& origin,
258                                       const std::string& native_host_name,
259                                       LaunchedCallback callback) const {
260  core_->Launch(origin, native_host_name, callback);
261}
262
263}  // namespace
264
265// static
266scoped_ptr<NativeProcessLauncher> NativeProcessLauncher::CreateDefault(
267    gfx::NativeView native_view) {
268  return scoped_ptr<NativeProcessLauncher>(
269      new NativeProcessLauncherImpl(native_view));
270}
271
272}  // namespace extensions
273