root/chrome/browser/extensions/api/messaging/native_message_process_host.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. IsHostAllowed
  2. write_pending_
  3. Create
  4. CreateWithLauncher
  5. LaunchHostProcess
  6. OnHostProcessLaunched
  7. Send
  8. OnFileCanReadWithoutBlocking
  9. OnFileCanWriteWithoutBlocking
  10. ReadNowForTesting
  11. WaitRead
  12. DoRead
  13. OnRead
  14. HandleReadResult
  15. ProcessIncomingData
  16. DoWrite
  17. HandleWriteResult
  18. OnWritten
  19. Close

// 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_message_process_host.h"

#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/platform_file.h"
#include "base/prefs/pref_service.h"
#include "base/process/kill.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h"
#include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
#include "chrome/common/chrome_version_info.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/pref_names.h"
#include "extensions/common/constants.h"
#include "extensions/common/features/feature.h"
#include "net/base/file_stream.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "url/gurl.h"

namespace {

// Maximum message size in bytes for messages received from Native Messaging
// hosts. Message size is limited mainly to prevent Chrome from crashing when
// native application misbehaves (e.g. starts writing garbage to the pipe).
const size_t kMaximumMessageSize = 1024 * 1024;

// Message header contains 4-byte integer size of the message.
const size_t kMessageHeaderSize = 4;

// Size of the buffer to be allocated for each read.
const size_t kReadBufferSize = 4096;

const char kFailedToStartError[] = "Failed to start native messaging host.";
const char kInvalidNameError[] =
    "Invalid native messaging host name specified.";
const char kNativeHostExited[] = "Native host has exited.";
const char kNotFoundError[] = "Specified native messaging host not found.";
const char kForbiddenError[] =
    "Access to the specified native messaging host is forbidden.";
const char kHostInputOuputError[] =
    "Error when communicating with the native messaging host.";

}  // namespace

namespace extensions {

// static
NativeMessageProcessHost::PolicyPermission
NativeMessageProcessHost::IsHostAllowed(const PrefService* pref_service,
                                        const std::string& native_host_name) {
  NativeMessageProcessHost::PolicyPermission allow_result = ALLOW_ALL;
  if (pref_service->IsManagedPreference(
          pref_names::kNativeMessagingUserLevelHosts)) {
    if (!pref_service->GetBoolean(pref_names::kNativeMessagingUserLevelHosts))
      allow_result = ALLOW_SYSTEM_ONLY;
  }

  // All native messaging hosts are allowed if there is no blacklist.
  if (!pref_service->IsManagedPreference(pref_names::kNativeMessagingBlacklist))
    return allow_result;
  const base::ListValue* blacklist =
      pref_service->GetList(pref_names::kNativeMessagingBlacklist);
  if (!blacklist)
    return allow_result;

  // Check if the name or the wildcard is in the blacklist.
  base::StringValue name_value(native_host_name);
  base::StringValue wildcard_value("*");
  if (blacklist->Find(name_value) == blacklist->end() &&
      blacklist->Find(wildcard_value) == blacklist->end()) {
    return allow_result;
  }

  // The native messaging host is blacklisted. Check the whitelist.
  if (pref_service->IsManagedPreference(
          pref_names::kNativeMessagingWhitelist)) {
    const base::ListValue* whitelist =
        pref_service->GetList(pref_names::kNativeMessagingWhitelist);
    if (whitelist && whitelist->Find(name_value) != whitelist->end())
      return allow_result;
  }

  return DISALLOW;
}

NativeMessageProcessHost::NativeMessageProcessHost(
    base::WeakPtr<Client> weak_client_ui,
    const std::string& source_extension_id,
    const std::string& native_host_name,
    int destination_port,
    scoped_ptr<NativeProcessLauncher> launcher)
    : weak_client_ui_(weak_client_ui),
      source_extension_id_(source_extension_id),
      native_host_name_(native_host_name),
      destination_port_(destination_port),
      launcher_(launcher.Pass()),
      closed_(false),
      process_handle_(base::kNullProcessHandle),
#if defined(OS_POSIX)
      read_file_(base::kInvalidPlatformFileValue),
#endif
      read_pending_(false),
      write_pending_(false) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // It's safe to use base::Unretained() here because NativeMessagePort always
  // deletes us on the IO thread.
  content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE,
      base::Bind(&NativeMessageProcessHost::LaunchHostProcess,
                 base::Unretained(this)));
}

NativeMessageProcessHost::~NativeMessageProcessHost() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  Close(std::string());
}

// static
scoped_ptr<NativeMessageProcessHost> NativeMessageProcessHost::Create(
    gfx::NativeView native_view,
    base::WeakPtr<Client> weak_client_ui,
    const std::string& source_extension_id,
    const std::string& native_host_name,
    int destination_port,
    bool allow_user_level) {
  return CreateWithLauncher(weak_client_ui, source_extension_id,
                            native_host_name, destination_port,
                            NativeProcessLauncher::CreateDefault(
                                allow_user_level, native_view));
}

// static
scoped_ptr<NativeMessageProcessHost>
NativeMessageProcessHost::CreateWithLauncher(
    base::WeakPtr<Client> weak_client_ui,
    const std::string& source_extension_id,
    const std::string& native_host_name,
    int destination_port,
    scoped_ptr<NativeProcessLauncher> launcher) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  scoped_ptr<NativeMessageProcessHost> process(new NativeMessageProcessHost(
      weak_client_ui, source_extension_id, native_host_name,
      destination_port, launcher.Pass()));

  return process.Pass();
}

void NativeMessageProcessHost::LaunchHostProcess() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  GURL origin(std::string(kExtensionScheme) + "://" + source_extension_id_);
  launcher_->Launch(origin, native_host_name_,
                    base::Bind(&NativeMessageProcessHost::OnHostProcessLaunched,
                               base::Unretained(this)));
}

void NativeMessageProcessHost::OnHostProcessLaunched(
    NativeProcessLauncher::LaunchResult result,
    base::ProcessHandle process_handle,
    base::File read_file,
    base::File write_file) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  switch (result) {
    case NativeProcessLauncher::RESULT_INVALID_NAME:
      Close(kInvalidNameError);
      return;
    case NativeProcessLauncher::RESULT_NOT_FOUND:
      Close(kNotFoundError);
      return;
    case NativeProcessLauncher::RESULT_FORBIDDEN:
      Close(kForbiddenError);
      return;
    case NativeProcessLauncher::RESULT_FAILED_TO_START:
      Close(kFailedToStartError);
      return;
    case NativeProcessLauncher::RESULT_SUCCESS:
      break;
  }

  process_handle_ = process_handle;
#if defined(OS_POSIX)
  // This object is not the owner of the file so it should not keep an fd.
  read_file_ = read_file.GetPlatformFile();
#endif

  scoped_refptr<base::TaskRunner> task_runner(
      content::BrowserThread::GetBlockingPool()->
          GetTaskRunnerWithShutdownBehavior(
              base::SequencedWorkerPool::SKIP_ON_SHUTDOWN));

  read_stream_.reset(new net::FileStream(
      read_file.TakePlatformFile(),
      base::PLATFORM_FILE_READ | base::PLATFORM_FILE_ASYNC, NULL,
      task_runner));
  write_stream_.reset(new net::FileStream(
      write_file.TakePlatformFile(),
      base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC, NULL,
      task_runner));

  WaitRead();
  DoWrite();
}

void NativeMessageProcessHost::Send(const std::string& json) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (closed_)
    return;

  // Allocate new buffer for the message.
  scoped_refptr<net::IOBufferWithSize> buffer =
      new net::IOBufferWithSize(json.size() + kMessageHeaderSize);

  // Copy size and content of the message to the buffer.
  COMPILE_ASSERT(sizeof(uint32) == kMessageHeaderSize, incorrect_header_size);
  *reinterpret_cast<uint32*>(buffer->data()) = json.size();
  memcpy(buffer->data() + kMessageHeaderSize, json.data(), json.size());

  // Push new message to the write queue.
  write_queue_.push(buffer);

  // Send() may be called before the host process is started. In that case the
  // message will be written when OnHostProcessLaunched() is called. If it's
  // already started then write the message now.
  if (write_stream_)
    DoWrite();
}

#if defined(OS_POSIX)
void NativeMessageProcessHost::OnFileCanReadWithoutBlocking(int fd) {
  DCHECK_EQ(fd, read_file_);
  DoRead();
}

void NativeMessageProcessHost::OnFileCanWriteWithoutBlocking(int fd) {
  NOTREACHED();
}
#endif  // !defined(OS_POSIX)

void NativeMessageProcessHost::ReadNowForTesting() {
  DoRead();
}

void NativeMessageProcessHost::WaitRead() {
  if (closed_)
    return;

  DCHECK(!read_pending_);

  // On POSIX FileStream::Read() uses blocking thread pool, so it's better to
  // wait for the file to become readable before calling DoRead(). Otherwise it
  // would always be consuming one thread in the thread pool. On Windows
  // FileStream uses overlapped IO, so that optimization isn't necessary there.
#if defined(OS_POSIX)
  base::MessageLoopForIO::current()->WatchFileDescriptor(
    read_file_, false /* persistent */,
    base::MessageLoopForIO::WATCH_READ, &read_watcher_, this);
#else  // defined(OS_POSIX)
  DoRead();
#endif  // defined(!OS_POSIX)
}

void NativeMessageProcessHost::DoRead() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  while (!closed_ && !read_pending_) {
    read_buffer_ = new net::IOBuffer(kReadBufferSize);
    int result = read_stream_->Read(
        read_buffer_.get(),
        kReadBufferSize,
        base::Bind(&NativeMessageProcessHost::OnRead, base::Unretained(this)));
    HandleReadResult(result);
  }
}

void NativeMessageProcessHost::OnRead(int result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  DCHECK(read_pending_);
  read_pending_ = false;

  HandleReadResult(result);
  WaitRead();
}

void NativeMessageProcessHost::HandleReadResult(int result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (closed_)
    return;

  if (result > 0) {
    ProcessIncomingData(read_buffer_->data(), result);
  } else if (result == net::ERR_IO_PENDING) {
    read_pending_ = true;
  } else if (result == 0 || result == net::ERR_CONNECTION_RESET) {
    // On Windows we get net::ERR_CONNECTION_RESET for a broken pipe, while on
    // Posix read() returns 0 in that case.
    Close(kNativeHostExited);
  } else {
    LOG(ERROR) << "Error when reading from Native Messaging host: " << result;
    Close(kHostInputOuputError);
  }
}

void NativeMessageProcessHost::ProcessIncomingData(
    const char* data, int data_size) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  incoming_data_.append(data, data_size);

  while (true) {
    if (incoming_data_.size() < kMessageHeaderSize)
      return;

    size_t message_size =
        *reinterpret_cast<const uint32*>(incoming_data_.data());

    if (message_size > kMaximumMessageSize) {
      LOG(ERROR) << "Native Messaging host tried sending a message that is "
                 << message_size << " bytes long.";
      Close(kHostInputOuputError);
      return;
    }

    if (incoming_data_.size() < message_size + kMessageHeaderSize)
      return;

    content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
        base::Bind(&Client::PostMessageFromNativeProcess, weak_client_ui_,
            destination_port_,
            incoming_data_.substr(kMessageHeaderSize, message_size)));

    incoming_data_.erase(0, kMessageHeaderSize + message_size);
  }
}

void NativeMessageProcessHost::DoWrite() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  while (!write_pending_ && !closed_) {
    if (!current_write_buffer_.get() ||
        !current_write_buffer_->BytesRemaining()) {
      if (write_queue_.empty())
        return;
      current_write_buffer_ = new net::DrainableIOBuffer(
          write_queue_.front().get(), write_queue_.front()->size());
      write_queue_.pop();
    }

    int result =
        write_stream_->Write(current_write_buffer_.get(),
                             current_write_buffer_->BytesRemaining(),
                             base::Bind(&NativeMessageProcessHost::OnWritten,
                                        base::Unretained(this)));
    HandleWriteResult(result);
  }
}

void NativeMessageProcessHost::HandleWriteResult(int result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (result <= 0) {
    if (result == net::ERR_IO_PENDING) {
      write_pending_ = true;
    } else {
      LOG(ERROR) << "Error when writing to Native Messaging host: " << result;
      Close(kHostInputOuputError);
    }
    return;
  }

  current_write_buffer_->DidConsume(result);
}

void NativeMessageProcessHost::OnWritten(int result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  DCHECK(write_pending_);
  write_pending_ = false;

  HandleWriteResult(result);
  DoWrite();
}

void NativeMessageProcessHost::Close(const std::string& error_message) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (!closed_) {
    closed_ = true;
    read_stream_.reset();
    write_stream_.reset();
    content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
        base::Bind(&Client::CloseChannel, weak_client_ui_,
                   destination_port_, error_message));
  }

  if (process_handle_ != base::kNullProcessHandle) {
    // Kill the host process if necessary to make sure we don't leave zombies.
    // On OSX base::EnsureProcessTerminated() may block, so we have to post a
    // task on the blocking pool.
#if defined(OS_MACOSX)
    content::BrowserThread::PostBlockingPoolTask(
        FROM_HERE, base::Bind(&base::EnsureProcessTerminated, process_handle_));
#else
    base::EnsureProcessTerminated(process_handle_);
#endif
    process_handle_ = base::kNullProcessHandle;
  }
}

}  // namespace extensions

/* [<][>][^][v][top][bottom][index][help] */