Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4965)

Unified Diff: chrome/browser/extensions/native_message_process.cc

Issue 10818013: Native Messaging! (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Everything is Different! Merged with MessageService Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/extensions/native_message_process.cc
diff --git a/chrome/browser/extensions/native_message_process.cc b/chrome/browser/extensions/native_message_process.cc
new file mode 100644
index 0000000000000000000000000000000000000000..b377b8c8a6047734d1c08bdb272e4dd8f51cb8cd
--- /dev/null
+++ b/chrome/browser/extensions/native_message_process.cc
@@ -0,0 +1,294 @@
+// 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/native_message_process.h"
+
+#include <unistd.h>
+
+#include "base/command_line.h"
+#include "base/file_path.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/pickle.h"
+#include "base/process_util.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/message_service.h"
+#include "chrome/common/chrome_paths.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace {
+
+// TODO(eriq): Figure out good values for these constants.
+const int kExitTimeoutMS = 100;
+const uint32 kMaxMessageDataLength = 4096;
Matt Perry 2012/08/09 02:13:00 I imagine extensions will want much larger sizes,
eaugusti 2012/08/13 23:22:34 How about 10MB?
Matt Perry 2012/08/16 00:01:36 SGTM
+const char kNativeClientDir[] = "Native Hosts";
+
+} // namespace
+
+namespace extensions {
+
+NativeMessageProcess::MessageData::MessageData() {
+}
+
+NativeMessageProcess::MessageData::MessageData(MessageType message_type,
+ std::string message_data)
+ : type(message_type),
+ data(message_data) {
+}
+
+NativeMessageProcess::NativeMessageProcess(
+ MessageService* service,
+ int dest_port,
+ base::ProcessHandle handle,
+ int read_fd,
+ int write_fd,
+ bool is_send_message)
+ : service_(service),
+ dest_port_(dest_port),
+ handle_(handle),
+ watching_write_(false),
+ read_fd_(read_fd),
+ write_fd_(write_fd),
+ is_send_message_(is_send_message) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
+ scoped_read_fd_.reset(&read_fd_);
+ scoped_write_fd_.reset(&write_fd_);
+ // Always watch the read end.
+ MessageLoopForIO::current()->WatchFileDescriptor(read_fd_,
+ true, /* persistent */
+ MessageLoopForIO::WATCH_READ,
+ &read_watcher_,
+ this);
+}
+
+NativeMessageProcess::~NativeMessageProcess() {
+ read_watcher_.StopWatchingFileDescriptor();
+ write_watcher_.StopWatchingFileDescriptor();
+
+ // Close the fds now.
+ scoped_read_fd_.reset();
+ scoped_write_fd_.reset();
+
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&NativeMessageProcess::KillProcess, handle_));
+}
+
+// static
+NativeMessageProcess* NativeMessageProcess::Create(
+ const std::string& native_app_name,
+ const std::string& connection_message,
+ MessageService* service,
+ int dest_port,
+ MessageType type) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
+
+ DCHECK(type == TYPE_SEND_MESSAGE_REQUEST || type == TYPE_CONNECT);
+
+ base::FileHandleMappingVector fd_map;
+
+ int read_pipe_fds[2];
+ if (HANDLE_EINTR(pipe(read_pipe_fds)) != 0) {
+ LOG(ERROR) << "Bad read pipe";
+ return NULL;
+ }
+ file_util::ScopedFD read_pipe_read_fd(&read_pipe_fds[0]);
+ file_util::ScopedFD read_pipe_write_fd(&read_pipe_fds[1]);
+ fd_map.push_back(std::make_pair(*read_pipe_write_fd, STDOUT_FILENO));
+
+ int write_pipe_fds[2];
+ if (HANDLE_EINTR(pipe(write_pipe_fds)) != 0) {
+ LOG(ERROR) << "Bad write pipe";
+ return NULL;
+ }
+ file_util::ScopedFD write_pipe_read_fd(&write_pipe_fds[0]);
+ file_util::ScopedFD write_pipe_write_fd(&write_pipe_fds[1]);
+ fd_map.push_back(std::make_pair(*write_pipe_read_fd, STDIN_FILENO));
+
+ FilePath client_path;
+ FilePath registered_client_dir;
+ CHECK(PathService::Get(chrome::DIR_USER_DATA, &registered_client_dir));
+ registered_client_dir = registered_client_dir.Append(kNativeClientDir);
+ client_path = registered_client_dir.Append(native_app_name);
+
+ // Make sure that the client is not trying to invoke something outside of the
+ // proper directory. Eg. '../../dangerous_something.exe'.
+ if (!file_util::ContainsPath(registered_client_dir, client_path)) {
+ LOG(ERROR) << "Paths are not accepted as native app names.";
+ return NULL;
+ }
+
+ CommandLine line(client_path);
+ base::ProcessHandle handle;
+ base::LaunchOptions options;
+ options.fds_to_remap = &fd_map;
+ if (!base::LaunchProcess(line, options, &handle)) {
+ LOG(ERROR) << "Error launching process";
+ return NULL;
+ }
+
+ // We will not be reading from the write pipe, nor writing from the read pipe.
+ write_pipe_read_fd.reset();
+ read_pipe_write_fd.reset();
+
+ NativeMessageProcess* process = new NativeMessageProcess(
+ service, dest_port, handle,
+ *read_pipe_read_fd.release(), *write_pipe_write_fd.release(),
+ type == TYPE_SEND_MESSAGE_REQUEST);
+
+ process->Send(type, connection_message);
+
+ return process;
+}
+
+void NativeMessageProcess::WatchWrite() {
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
+ if (!MessageLoopForIO::current()->WatchFileDescriptor(
+ write_fd_, true, /* persistent */
+ MessageLoopForIO::WATCH_WRITE, &write_watcher_, this)) {
+ LOG(ERROR) << "Error watching for the FD.";
+ }
+}
+
+void NativeMessageProcess::Send(MessageType type, const std::string& json) {
Matt Perry 2012/08/09 02:13:00 What thread is this called on? Assuming the answer
eaugusti 2012/08/13 23:22:34 Done.
+ pending_messages_.push_back(MessageData(type, json));
+ if (!watching_write_) {
+ watching_write_ = true;
+ content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE,
+ base::Bind(&NativeMessageProcess::WatchWrite, base::Unretained(this)));
+ }
+}
+
+void NativeMessageProcess::OnFileCanReadWithoutBlocking(int fd) {
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
+
+ // Make sure that the fd given to us is the same one we started with.
+ CHECK_EQ(fd, read_fd_);
+
+ // If this is a sendMessage request, stop trying to read after the first
+ // message.
+ if (is_send_message_)
+ read_watcher_.StopWatchingFileDescriptor();
+
+ MessageData data;
+ if (!ReadMessage(fd, &data)) {
+ LOG(ERROR) << "Bad Read";
+ return;
+ }
+
+ service_->PostMessageFromRenderer(dest_port_, data.data);
Matt Perry 2012/08/09 02:13:00 This must be called on the UI thread. Note you als
eaugusti 2012/08/13 23:22:34 Done.
+}
+
+void NativeMessageProcess::OnFileCanWriteWithoutBlocking(int fd) {
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
+
+ // Make sure that the fd given to us is the same one we started with.
+ CHECK_EQ(fd, write_fd_);
+
+ write_watcher_.StopWatchingFileDescriptor();
+ watching_write_ = false;
+
+ while (!pending_messages_.empty()) {
+ WriteMessage(fd, pending_messages_.back());
+ pending_messages_.pop_back();
+ }
+}
+
+// TODO(eriq): Update doc: byte (type) -> uint32.
+bool NativeMessageProcess::WriteMessage(int fd, const MessageData& data) {
+ Pickle pickle;
+
+ // Pickles will always pad bytes to 32-bit alignment, so just use a unit32.
+ pickle.WriteUInt32(data.type);
+
+ // Pickles write the length of a string before it as a uint32.
+ pickle.WriteString(data.data);
+
+ // Make sure that the pickle doesn't do any unexpected padding.
+ DCHECK(8 + data.data.length() == pickle.payload_size());
+
+ if (!file_util::WriteFileDescriptor(
+ fd,
+ const_cast<const Pickle*>(&pickle)->payload(),
+ pickle.payload_size())) {
+ LOG(ERROR) << "Error writing message to the native client.";
+ return false;
+ }
+
+ return true;
+}
+
+bool NativeMessageProcess::ReadMessage(int fd, MessageData* message) {
+ char data[kMaxMessageDataLength];
Matt Perry 2012/08/09 02:13:00 Better to heap-allocate this, especially if you up
eaugusti 2012/08/13 23:22:34 Done.
+
+ // Read the type (uint32) and length (uint32).
+ errno = 0;
+ if (!file_util::ReadFromFD(fd, data, 8)) {
+ LOG(ERROR) << "Error reading the message type and length.";
+ return false;
+ }
+
+ Pickle pickle;
+ pickle.WriteBytes(data, 8);
+ PickleIterator pickle_it(pickle);
+ uint32 type;
+ uint32 data_length;
+ if (!pickle_it.ReadUInt32(&type) || !pickle_it.ReadUInt32(&data_length)) {
+ LOG(ERROR) << "Error getting the message type and length from the pickle.";
+ return false;
+ }
+
+ if (type < 0 || type >= NUM_MESSAGE_TYPES) {
+ LOG(ERROR) << type << " is not a valid message type.";
+ return false;
+ }
+
+ if ((is_send_message_ && (type != TYPE_SEND_MESSAGE_RESPONSE)) ||
+ (!is_send_message_ && (type != TYPE_CONNECT_MESSAGE))) {
+ LOG(ERROR) << "Recieved a message of type " << type << ". "
+ << "Expecting a message of type "
+ << (is_send_message_ ? TYPE_SEND_MESSAGE_RESPONSE :
+ TYPE_CONNECT_MESSAGE);
+ return false;
+ }
+ message->type = static_cast<MessageType>(type);
+
+ if (data_length > kMaxMessageDataLength) {
+ LOG(ERROR) << data_length << " is too large for the length of a message. "
+ << "Max message size is " << kMaxMessageDataLength;
+ return false;
+ }
+
+ if (!file_util::ReadFromFD(fd, data, data_length)) {
+ LOG(ERROR) << "Error reading the json data.";
+ return false;
+ }
+ std::string read_data(data, data_length);
+ message->data.swap(read_data);
+
+ return true;
+}
+
+// static
+void NativeMessageProcess::KillProcess(base::ProcessHandle handle) {
Matt Perry 2012/08/09 02:13:00 This is a little draconian. Why do we need to be s
Aaron Boodman 2012/08/09 18:36:12 I don't think we need to kill it all. We can just
eaugusti 2012/08/13 23:22:34 Aaron and I were talking about it, and how about w
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
+ int exit_code = 0;
+ if (!base::WaitForExitCodeWithTimeout(
+ handle,
+ &exit_code,
+ base::TimeDelta::FromMilliseconds(kExitTimeoutMS))) {
+ // Kill the process.
+ if (!base::KillProcess(handle,
+ -1, /* error exit code */
+ false /* Don't wait for the process to exit */)) {
+ // Really kill the process.
+ base::EnsureProcessTerminated(handle);
+ }
+ }
+}
+
+} // namespace extensions

Powered by Google App Engine
This is Rietveld 408576698