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