1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/extensions/api/messaging/native_message_process_host.h" 6 7#include "base/bind.h" 8#include "base/files/file_path.h" 9#include "base/logging.h" 10#include "base/platform_file.h" 11#include "base/process/kill.h" 12#include "base/threading/sequenced_worker_pool.h" 13#include "base/values.h" 14#include "chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h" 15#include "chrome/browser/extensions/api/messaging/native_process_launcher.h" 16#include "chrome/common/chrome_version_info.h" 17#include "extensions/common/constants.h" 18#include "extensions/common/features/feature.h" 19#include "net/base/file_stream.h" 20#include "net/base/io_buffer.h" 21#include "net/base/net_errors.h" 22#include "net/base/net_util.h" 23#include "url/gurl.h" 24 25namespace { 26 27// Maximum message size in bytes for messages received from Native Messaging 28// hosts. Message size is limited mainly to prevent Chrome from crashing when 29// native application misbehaves (e.g. starts writing garbage to the pipe). 30const size_t kMaximumMessageSize = 1024 * 1024; 31 32// Message header contains 4-byte integer size of the message. 33const size_t kMessageHeaderSize = 4; 34 35// Size of the buffer to be allocated for each read. 36const size_t kReadBufferSize = 4096; 37 38const char kFailedToStartError[] = "Failed to start native messaging host."; 39const char kInvalidNameError[] = 40 "Invalid native messaging host name specified."; 41const char kNativeHostExited[] = "Native host has exited."; 42const char kNotFoundError[] = "Specified native messaging host not found."; 43const char kForbiddenError[] = 44 "Access to the specified native messaging host is forbidden."; 45const char kHostInputOuputError[] = 46 "Error when communicating with the native messaging host."; 47 48} // namespace 49 50namespace extensions { 51 52NativeMessageProcessHost::NativeMessageProcessHost( 53 base::WeakPtr<Client> weak_client_ui, 54 const std::string& source_extension_id, 55 const std::string& native_host_name, 56 int destination_port, 57 scoped_ptr<NativeProcessLauncher> launcher) 58 : weak_client_ui_(weak_client_ui), 59 source_extension_id_(source_extension_id), 60 native_host_name_(native_host_name), 61 destination_port_(destination_port), 62 launcher_(launcher.Pass()), 63 closed_(false), 64 process_handle_(base::kNullProcessHandle), 65 read_file_(base::kInvalidPlatformFileValue), 66 read_pending_(false), 67 write_pending_(false) { 68 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 69 70 // It's safe to use base::Unretained() here because NativeMessagePort always 71 // deletes us on the IO thread. 72 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, 73 base::Bind(&NativeMessageProcessHost::LaunchHostProcess, 74 base::Unretained(this))); 75} 76 77NativeMessageProcessHost::~NativeMessageProcessHost() { 78 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 79 Close(std::string()); 80} 81 82// static 83scoped_ptr<NativeMessageProcessHost> NativeMessageProcessHost::Create( 84 gfx::NativeView native_view, 85 base::WeakPtr<Client> weak_client_ui, 86 const std::string& source_extension_id, 87 const std::string& native_host_name, 88 int destination_port) { 89 return CreateWithLauncher(weak_client_ui, source_extension_id, 90 native_host_name, destination_port, 91 NativeProcessLauncher::CreateDefault(native_view)); 92} 93 94// static 95scoped_ptr<NativeMessageProcessHost> 96NativeMessageProcessHost::CreateWithLauncher( 97 base::WeakPtr<Client> weak_client_ui, 98 const std::string& source_extension_id, 99 const std::string& native_host_name, 100 int destination_port, 101 scoped_ptr<NativeProcessLauncher> launcher) { 102 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 103 104 scoped_ptr<NativeMessageProcessHost> process(new NativeMessageProcessHost( 105 weak_client_ui, source_extension_id, native_host_name, 106 destination_port, launcher.Pass())); 107 108 return process.Pass(); 109} 110 111void NativeMessageProcessHost::LaunchHostProcess() { 112 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 113 114 GURL origin(std::string(kExtensionScheme) + "://" + source_extension_id_); 115 launcher_->Launch(origin, native_host_name_, 116 base::Bind(&NativeMessageProcessHost::OnHostProcessLaunched, 117 base::Unretained(this))); 118} 119 120void NativeMessageProcessHost::OnHostProcessLaunched( 121 NativeProcessLauncher::LaunchResult result, 122 base::ProcessHandle process_handle, 123 base::PlatformFile read_file, 124 base::PlatformFile write_file) { 125 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 126 127 switch (result) { 128 case NativeProcessLauncher::RESULT_INVALID_NAME: 129 Close(kInvalidNameError); 130 return; 131 case NativeProcessLauncher::RESULT_NOT_FOUND: 132 Close(kNotFoundError); 133 return; 134 case NativeProcessLauncher::RESULT_FORBIDDEN: 135 Close(kForbiddenError); 136 return; 137 case NativeProcessLauncher::RESULT_FAILED_TO_START: 138 Close(kFailedToStartError); 139 return; 140 case NativeProcessLauncher::RESULT_SUCCESS: 141 break; 142 } 143 144 process_handle_ = process_handle; 145 read_file_ = read_file; 146 147 scoped_refptr<base::TaskRunner> task_runner( 148 content::BrowserThread::GetBlockingPool()-> 149 GetTaskRunnerWithShutdownBehavior( 150 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); 151 152 read_stream_.reset(new net::FileStream( 153 read_file, base::PLATFORM_FILE_READ | base::PLATFORM_FILE_ASYNC, NULL, 154 task_runner)); 155 write_stream_.reset(new net::FileStream( 156 write_file, base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC, NULL, 157 task_runner)); 158 159 WaitRead(); 160 DoWrite(); 161} 162 163void NativeMessageProcessHost::Send(const std::string& json) { 164 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 165 166 if (closed_) 167 return; 168 169 // Allocate new buffer for the message. 170 scoped_refptr<net::IOBufferWithSize> buffer = 171 new net::IOBufferWithSize(json.size() + kMessageHeaderSize); 172 173 // Copy size and content of the message to the buffer. 174 COMPILE_ASSERT(sizeof(uint32) == kMessageHeaderSize, incorrect_header_size); 175 *reinterpret_cast<uint32*>(buffer->data()) = json.size(); 176 memcpy(buffer->data() + kMessageHeaderSize, json.data(), json.size()); 177 178 // Push new message to the write queue. 179 write_queue_.push(buffer); 180 181 // Send() may be called before the host process is started. In that case the 182 // message will be written when OnHostProcessLaunched() is called. If it's 183 // already started then write the message now. 184 if (write_stream_) 185 DoWrite(); 186} 187 188#if defined(OS_POSIX) 189void NativeMessageProcessHost::OnFileCanReadWithoutBlocking(int fd) { 190 DCHECK_EQ(fd, read_file_); 191 DoRead(); 192} 193 194void NativeMessageProcessHost::OnFileCanWriteWithoutBlocking(int fd) { 195 NOTREACHED(); 196} 197#endif // !defined(OS_POSIX) 198 199void NativeMessageProcessHost::ReadNowForTesting() { 200 DoRead(); 201} 202 203void NativeMessageProcessHost::WaitRead() { 204 if (closed_) 205 return; 206 207 DCHECK(!read_pending_); 208 209 // On POSIX FileStream::Read() uses blocking thread pool, so it's better to 210 // wait for the file to become readable before calling DoRead(). Otherwise it 211 // would always be consuming one thread in the thread pool. On Windows 212 // FileStream uses overlapped IO, so that optimization isn't necessary there. 213#if defined(OS_POSIX) 214 base::MessageLoopForIO::current()->WatchFileDescriptor( 215 read_file_, false /* persistent */, base::MessageLoopForIO::WATCH_READ, 216 &read_watcher_, this); 217#else // defined(OS_POSIX) 218 DoRead(); 219#endif // defined(!OS_POSIX) 220} 221 222void NativeMessageProcessHost::DoRead() { 223 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 224 225 while (!closed_ && !read_pending_) { 226 read_buffer_ = new net::IOBuffer(kReadBufferSize); 227 int result = read_stream_->Read( 228 read_buffer_.get(), 229 kReadBufferSize, 230 base::Bind(&NativeMessageProcessHost::OnRead, base::Unretained(this))); 231 HandleReadResult(result); 232 } 233} 234 235void NativeMessageProcessHost::OnRead(int result) { 236 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 237 DCHECK(read_pending_); 238 read_pending_ = false; 239 240 HandleReadResult(result); 241 WaitRead(); 242} 243 244void NativeMessageProcessHost::HandleReadResult(int result) { 245 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 246 247 if (closed_) 248 return; 249 250 if (result > 0) { 251 ProcessIncomingData(read_buffer_->data(), result); 252 } else if (result == net::ERR_IO_PENDING) { 253 read_pending_ = true; 254 } else if (result == 0 || result == net::ERR_CONNECTION_RESET) { 255 // On Windows we get net::ERR_CONNECTION_RESET for a broken pipe, while on 256 // Posix read() returns 0 in that case. 257 Close(kNativeHostExited); 258 } else { 259 LOG(ERROR) << "Error when reading from Native Messaging host: " << result; 260 Close(kHostInputOuputError); 261 } 262} 263 264void NativeMessageProcessHost::ProcessIncomingData( 265 const char* data, int data_size) { 266 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 267 268 incoming_data_.append(data, data_size); 269 270 while (true) { 271 if (incoming_data_.size() < kMessageHeaderSize) 272 return; 273 274 size_t message_size = 275 *reinterpret_cast<const uint32*>(incoming_data_.data()); 276 277 if (message_size > kMaximumMessageSize) { 278 LOG(ERROR) << "Native Messaging host tried sending a message that is " 279 << message_size << " bytes long."; 280 Close(kHostInputOuputError); 281 return; 282 } 283 284 if (incoming_data_.size() < message_size + kMessageHeaderSize) 285 return; 286 287 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 288 base::Bind(&Client::PostMessageFromNativeProcess, weak_client_ui_, 289 destination_port_, 290 incoming_data_.substr(kMessageHeaderSize, message_size))); 291 292 incoming_data_.erase(0, kMessageHeaderSize + message_size); 293 } 294} 295 296void NativeMessageProcessHost::DoWrite() { 297 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 298 299 while (!write_pending_ && !closed_) { 300 if (!current_write_buffer_.get() || 301 !current_write_buffer_->BytesRemaining()) { 302 if (write_queue_.empty()) 303 return; 304 current_write_buffer_ = new net::DrainableIOBuffer( 305 write_queue_.front().get(), write_queue_.front()->size()); 306 write_queue_.pop(); 307 } 308 309 int result = 310 write_stream_->Write(current_write_buffer_.get(), 311 current_write_buffer_->BytesRemaining(), 312 base::Bind(&NativeMessageProcessHost::OnWritten, 313 base::Unretained(this))); 314 HandleWriteResult(result); 315 } 316} 317 318void NativeMessageProcessHost::HandleWriteResult(int result) { 319 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 320 321 if (result <= 0) { 322 if (result == net::ERR_IO_PENDING) { 323 write_pending_ = true; 324 } else { 325 LOG(ERROR) << "Error when writing to Native Messaging host: " << result; 326 Close(kHostInputOuputError); 327 } 328 return; 329 } 330 331 current_write_buffer_->DidConsume(result); 332} 333 334void NativeMessageProcessHost::OnWritten(int result) { 335 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 336 337 DCHECK(write_pending_); 338 write_pending_ = false; 339 340 HandleWriteResult(result); 341 DoWrite(); 342} 343 344void NativeMessageProcessHost::Close(const std::string& error_message) { 345 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 346 347 if (!closed_) { 348 closed_ = true; 349 read_stream_.reset(); 350 write_stream_.reset(); 351 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 352 base::Bind(&Client::CloseChannel, weak_client_ui_, 353 destination_port_, error_message)); 354 } 355 356 if (process_handle_ != base::kNullProcessHandle) { 357 // Kill the host process if necessary to make sure we don't leave zombies. 358 // On OSX base::EnsureProcessTerminated() may block, so we have to post a 359 // task on the blocking pool. 360#if defined(OS_MACOSX) 361 content::BrowserThread::PostBlockingPoolTask( 362 FROM_HERE, base::Bind(&base::EnsureProcessTerminated, process_handle_)); 363#else 364 base::EnsureProcessTerminated(process_handle_); 365#endif 366 process_handle_ = base::kNullProcessHandle; 367 } 368} 369 370} // namespace extensions 371