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, ®istered_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 |