Chromium Code Reviews| 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..d9afad3f2c1c1428c01ca7dccb8c3b030f142f98 |
| --- /dev/null |
| +++ b/chrome/browser/extensions/native_message_process.cc |
| @@ -0,0 +1,277 @@ |
| +// 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" |
| +#include "content/public/common/result_codes.h" |
| + |
| +namespace { |
| + |
| +const int kExitTimeoutMS = 5000; |
| +const uint32 kMaxMessageDataLength = 10 * 1024 * 1024; |
| +const char kNativeClientDir[] = "Native Hosts"; |
|
Aaron Boodman
2012/08/15 03:39:21
kNativeHostsDirectoryName. Yeah it's longer. Life'
eaugusti
2012/08/21 23:08:34
Ha, done.
|
| + |
| +} // namespace |
| + |
| +namespace extensions { |
| + |
| +NativeMessageProcess::MessageData::MessageData() { |
| +} |
| + |
| +NativeMessageProcess::MessageData::MessageData(MessageType message_type, |
| + std::string message_data) |
| + : type(message_type), |
| + data(message_data) { |
| +} |
| + |
| +NativeMessageProcess::NativeMessageProcess( |
| + base::WeakPtr<MessageService> weak_service, |
| + int dest_port, |
|
Aaron Boodman
2012/08/15 03:39:21
Nit: avoid abbreviations; I would prefer destinati
eaugusti
2012/08/21 23:08:34
Done.
|
| + base::ProcessHandle handle, |
|
Aaron Boodman
2012/08/15 03:39:21
Nit: native_process_handle
eaugusti
2012/08/21 23:08:34
Done.
|
| + int read_fd, |
| + int write_fd, |
| + bool is_send_message) |
| + : weak_service_(weak_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() { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); |
| + read_watcher_.StopWatchingFileDescriptor(); |
|
Aaron Boodman
2012/08/15 03:39:21
You probably don't need to do this manually.
eaugusti
2012/08/21 23:08:34
Done.
|
| + write_watcher_.StopWatchingFileDescriptor(); |
| + |
| + // Close the fds now. |
| + scoped_read_fd_.reset(); |
|
Aaron Boodman
2012/08/15 03:39:21
You probably don't need to do this manually.
eaugusti
2012/08/21 23:08:34
Done.
|
| + scoped_write_fd_.reset(); |
| + |
| + // Give the process some time to shutdown, then try and kill it. |
| + content::BrowserThread::PostDelayedTask( |
| + content::BrowserThread::FILE, |
| + FROM_HERE, |
| + base::Bind(base::IgnoreResult(&base::KillProcess), |
| + handle_, |
| + content::RESULT_CODE_NORMAL_EXIT, |
| + false /* don't wait for exit */), |
| + base::TimeDelta::FromMilliseconds(kExitTimeoutMS)); |
| +} |
| + |
| +// static |
| +void NativeMessageProcess::Create( |
| + base::WeakPtr<MessageService> weak_service, |
| + const std::string& native_app_name, |
| + const std::string& connection_message, |
| + int dest_port, |
| + MessageType type, |
| + base::Callback<void(NativeMessageProcess*)> callback) { |
| + 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]; |
|
Aaron Boodman
2012/08/15 03:39:21
zero init with = {0};
eaugusti
2012/08/21 23:08:34
Done.
|
| + if (HANDLE_EINTR(pipe(read_pipe_fds)) != 0) { |
| + LOG(ERROR) << "Bad read pipe"; |
| + return; |
| + } |
| + 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]; |
|
Aaron Boodman
2012/08/15 03:39:21
initialize
eaugusti
2012/08/21 23:08:34
Done.
|
| + if (HANDLE_EINTR(pipe(write_pipe_fds)) != 0) { |
| + LOG(ERROR) << "Bad write pipe"; |
| + return; |
| + } |
| + 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; |
|
Aaron Boodman
2012/08/15 03:39:21
Suggestion: native_host_program
eaugusti
2012/08/21 23:08:34
Done.
|
| + FilePath registered_client_dir; |
|
Aaron Boodman
2012/08/15 03:39:21
Naming suggestion: native_host_registry
eaugusti
2012/08/21 23:08:34
Done.
|
| + 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."; |
|
Aaron Boodman
2012/08/15 03:39:21
LOG(ERROR) << "Could not find native host: " << na
eaugusti
2012/08/21 23:08:34
Done.
|
| + return; |
| + } |
| + |
| + 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; |
| + } |
| + |
| + // 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( |
| + weak_service, dest_port, handle, |
| + *read_pipe_read_fd.release(), *write_pipe_write_fd.release(), |
| + type == TYPE_SEND_MESSAGE_REQUEST); |
| + process->SendImpl(type, connection_message); |
| + |
| + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
| + base::Bind(callback, process)); |
|
Aaron Boodman
2012/08/15 03:39:21
bad indent. This line should line up with 'content
Matt Perry
2012/08/16 00:01:36
I don't think the style guide actually requires th
eaugusti
2012/08/21 23:08:35
Done.
|
| +} |
| + |
| +void NativeMessageProcess::SendImpl(MessageType type, const std::string& json) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); |
| + pending_messages_.push_back(MessageData(type, json)); |
| + if (!watching_write_) { |
| + watching_write_ = true; |
| + if (!MessageLoopForIO::current()->WatchFileDescriptor( |
|
Aaron Boodman
2012/08/15 03:39:21
Do we even need the async stuff for writing? Writi
eaugusti
2012/08/21 23:08:35
I think we can get away without it. Looking at ipc
|
| + write_fd_, true, /* persistent */ |
| + MessageLoopForIO::WATCH_WRITE, &write_watcher_, this)) { |
| + LOG(ERROR) << "Error watching for the FD."; |
| + } |
| + } |
| +} |
| + |
| +void NativeMessageProcess::OnFileCanReadWithoutBlocking(int fd) { |
| + DCHECK(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; |
| + } |
| + |
| + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
| + base::Bind(&MessageService::PostMessageFromRenderer, weak_service_, |
| + dest_port_, data.data)); |
| +} |
| + |
| +void NativeMessageProcess::OnFileCanWriteWithoutBlocking(int fd) { |
| + DCHECK(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(); |
| + } |
| +} |
| + |
| +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. |
| + CHECK(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) { |
| + // Read the type (uint32) and length (uint32). |
| + char message_meta_data[8]; |
| + if (!file_util::ReadFromFD(fd, message_meta_data, 8)) { |
|
Aaron Boodman
2012/08/15 03:39:21
What about incomplete messages? It seems like ther
eaugusti
2012/08/21 23:08:35
ReadFromFD protects against partial reads by just
|
| + LOG(ERROR) << "Error reading the message type and length."; |
| + return false; |
| + } |
| + |
| + Pickle pickle; |
| + pickle.WriteBytes(message_meta_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; |
| + } |
| + |
| + std::string str_data(data_length, '\0'); |
| + if (!file_util::ReadFromFD(fd, const_cast<char*>(str_data.data()), |
|
Matt Perry
2012/08/16 00:01:36
use &str_data[0]
eaugusti
2012/08/21 23:08:35
Done.
|
| + data_length)) { |
| + LOG(ERROR) << "Error reading the json data."; |
| + return false; |
| + } |
| + message->data.swap(str_data); |
| + |
| + return true; |
| +} |
| + |
| +} // namespace extensions |