blob: 0f8992e7c92904cac3fb1da326e1b6cd11de94ec [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
namespace extensions {
namespace {
// Name of the command line switch used to pass handle of the native view to
// the native messaging host.
const char kParentWindowSwitchName[] = "parent-window";
base::FilePath GetHostManifestPathFromCommandLine(
const std::string& native_host_name) {
const std::string& value =
CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kNativeMessagingHosts);
if (value.empty())
return base::FilePath();
std::vector<std::string> hosts;
base::SplitString(value, ',', &hosts);
for (size_t i = 0; i < hosts.size(); ++i) {
std::vector<std::string> key_and_value;
base::SplitString(hosts[i], '=', &key_and_value);
if (key_and_value.size() != 2)
continue;
if (key_and_value[0] == native_host_name)
return base::FilePath::FromUTF8Unsafe(key_and_value[1]);
}
return base::FilePath();
}
// Default implementation on NativeProcessLauncher interface.
class NativeProcessLauncherImpl : public NativeProcessLauncher {
public:
explicit NativeProcessLauncherImpl(gfx::NativeView native_view);
virtual ~NativeProcessLauncherImpl();
virtual void Launch(const GURL& origin,
const std::string& native_host_name,
LaunchedCallback callback) const OVERRIDE;
private:
class Core : public base::RefCountedThreadSafe<Core> {
public:
explicit Core(gfx::NativeView native_view);
void Launch(const GURL& origin,
const std::string& native_host_name,
LaunchedCallback callback);
void Detach();
private:
friend class base::RefCountedThreadSafe<Core>;
virtual ~Core();
void DoLaunchOnThreadPool(const GURL& origin,
const std::string& native_host_name,
LaunchedCallback callback);
void PostErrorResult(const LaunchedCallback& callback, LaunchResult error);
void PostResult(const LaunchedCallback& callback,
base::PlatformFile read_file,
base::PlatformFile write_file);
void CallCallbackOnIOThread(LaunchedCallback callback,
LaunchResult result,
base::PlatformFile read_file,
base::PlatformFile write_file);
bool detached_;
// Handle of the native view corrsponding to the extension.
gfx::NativeView native_view_;
DISALLOW_COPY_AND_ASSIGN(Core);
};
scoped_refptr<Core> core_;
DISALLOW_COPY_AND_ASSIGN(NativeProcessLauncherImpl);
};
NativeProcessLauncherImpl::Core::Core(gfx::NativeView native_view)
: detached_(false),
native_view_(native_view) {
}
NativeProcessLauncherImpl::Core::~Core() {
DCHECK(detached_);
}
void NativeProcessLauncherImpl::Core::Detach() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
detached_ = true;
}
void NativeProcessLauncherImpl::Core::Launch(
const GURL& origin,
const std::string& native_host_name,
LaunchedCallback callback) {
content::BrowserThread::PostBlockingPoolTask(
FROM_HERE, base::Bind(&Core::DoLaunchOnThreadPool, this,
origin, native_host_name, callback));
}
void NativeProcessLauncherImpl::Core::DoLaunchOnThreadPool(
const GURL& origin,
const std::string& native_host_name,
LaunchedCallback callback) {
DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
if (!NativeMessagingHostManifest::IsValidName(native_host_name)) {
PostErrorResult(callback, RESULT_INVALID_NAME);
return;
}
std::string error_message;
scoped_ptr<NativeMessagingHostManifest> manifest;
// First check if the manifest location is specified in the command line.
base::FilePath manifest_path =
GetHostManifestPathFromCommandLine(native_host_name);
if (manifest_path.empty())
manifest_path = FindManifest(native_host_name, &error_message);
if (manifest_path.empty()) {
LOG(ERROR) << "Can't find manifest for native messaging host "
<< native_host_name;
PostErrorResult(callback, RESULT_NOT_FOUND);
return;
}
manifest = NativeMessagingHostManifest::Load(manifest_path, &error_message);
if (!manifest) {
LOG(ERROR) << "Failed to load manifest for native messaging host "
<< native_host_name << ": " << error_message;
PostErrorResult(callback, RESULT_NOT_FOUND);
return;
}
if (manifest->name() != native_host_name) {
LOG(ERROR) << "Failed to load manifest for native messaging host "
<< native_host_name
<< ": Invalid name specified in the manifest.";
PostErrorResult(callback, RESULT_NOT_FOUND);
return;
}
if (!manifest->allowed_origins().MatchesSecurityOrigin(origin)) {
// Not an allowed origin.
PostErrorResult(callback, RESULT_FORBIDDEN);
return;
}
base::FilePath host_path = manifest->path();
if (!host_path.IsAbsolute()) {
// On Windows host path is allowed to be relative to the location of the
// manifest file. On all other platforms the path must be absolute.
#if defined(OS_WIN)
host_path = manifest_path.DirName().Append(host_path);
#else // defined(OS_WIN)
LOG(ERROR) << "Native messaging host path must be absolute for "
<< native_host_name;
PostErrorResult(callback, RESULT_NOT_FOUND);
return;
#endif // !defined(OS_WIN)
}
CommandLine command_line(host_path);
command_line.AppendArg(origin.spec());
// Pass handle of the native view window to the native messaging host. This
// way the host will be able to create properly focused UI windows.
#if defined(OS_WIN)
int64 window_handle = reinterpret_cast<intptr_t>(native_view_);
command_line.AppendSwitchASCII(kParentWindowSwitchName,
base::Int64ToString(window_handle));
#endif // !defined(OS_WIN)
base::PlatformFile read_file;
base::PlatformFile write_file;
if (NativeProcessLauncher::LaunchNativeProcess(
command_line, &read_file, &write_file)) {
PostResult(callback, read_file, write_file);
} else {
PostErrorResult(callback, RESULT_FAILED_TO_START);
}
}
void NativeProcessLauncherImpl::Core::CallCallbackOnIOThread(
LaunchedCallback callback,
LaunchResult result,
base::PlatformFile read_file,
base::PlatformFile write_file) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
if (detached_) {
if (read_file != base::kInvalidPlatformFileValue)
base::ClosePlatformFile(read_file);
if (write_file != base::kInvalidPlatformFileValue)
base::ClosePlatformFile(write_file);
return;
}
callback.Run(result, read_file, write_file);
}
void NativeProcessLauncherImpl::Core::PostErrorResult(
const LaunchedCallback& callback,
LaunchResult error) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&NativeProcessLauncherImpl::Core::CallCallbackOnIOThread,
this, callback, error,
base::kInvalidPlatformFileValue,
base::kInvalidPlatformFileValue));
}
void NativeProcessLauncherImpl::Core::PostResult(
const LaunchedCallback& callback,
base::PlatformFile read_file,
base::PlatformFile write_file) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&NativeProcessLauncherImpl::Core::CallCallbackOnIOThread,
this, callback, RESULT_SUCCESS, read_file, write_file));
}
NativeProcessLauncherImpl::NativeProcessLauncherImpl(
gfx::NativeView native_view)
: core_(new Core(native_view)) {
}
NativeProcessLauncherImpl::~NativeProcessLauncherImpl() {
core_->Detach();
}
void NativeProcessLauncherImpl::Launch(const GURL& origin,
const std::string& native_host_name,
LaunchedCallback callback) const {
core_->Launch(origin, native_host_name, callback);
}
} // namespace
// static
scoped_ptr<NativeProcessLauncher> NativeProcessLauncher::CreateDefault(
gfx::NativeView native_view) {
return scoped_ptr<NativeProcessLauncher>(
new NativeProcessLauncherImpl(native_view));
}
} // namespace extensions