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 "base/bind.h"
6#include "base/files/file.h"
7#include "base/files/file_path.h"
8#include "base/files/file_util.h"
9#include "base/files/scoped_file.h"
10#include "base/files/scoped_temp_dir.h"
11#include "base/json/json_reader.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/memory/weak_ptr.h"
14#include "base/message_loop/message_loop.h"
15#include "base/rand_util.h"
16#include "base/run_loop.h"
17#include "base/strings/stringprintf.h"
18#include "base/test/test_timeouts.h"
19#include "base/threading/platform_thread.h"
20#include "base/threading/sequenced_worker_pool.h"
21#include "base/time/time.h"
22#include "chrome/browser/extensions/api/messaging/native_message_process_host.h"
23#include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
24#include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
25#include "chrome/common/chrome_version_info.h"
26#include "chrome/common/extensions/features/feature_channel.h"
27#include "content/public/browser/browser_thread.h"
28#include "content/public/test/test_browser_thread_bundle.h"
29#include "extensions/common/extension.h"
30#include "testing/gtest/include/gtest/gtest.h"
31
32#if defined(OS_WIN)
33#include <windows.h>
34#include "base/win/scoped_handle.h"
35#else
36#include <unistd.h>
37#endif
38
39using content::BrowserThread;
40
41namespace {
42
43const char kTestMessage[] = "{\"text\": \"Hello.\"}";
44
45}  // namespace
46
47namespace extensions {
48
49class FakeLauncher : public NativeProcessLauncher {
50 public:
51  FakeLauncher(base::File read_file, base::File write_file)
52      : read_file_(read_file.Pass()),
53        write_file_(write_file.Pass()) {
54  }
55
56  static scoped_ptr<NativeProcessLauncher> Create(base::FilePath read_file,
57                                                  base::FilePath write_file) {
58    int read_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
59    int write_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;
60#if !defined(OS_POSIX)
61    read_flags |= base::File::FLAG_ASYNC;
62    write_flags |= base::File::FLAG_ASYNC;
63#endif
64    return scoped_ptr<NativeProcessLauncher>(new FakeLauncher(
65        base::File(read_file, read_flags),
66        base::File(write_file, write_flags)));
67  }
68
69  static scoped_ptr<NativeProcessLauncher> CreateWithPipeInput(
70      base::File read_pipe,
71      base::FilePath write_file) {
72    int write_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;
73#if !defined(OS_POSIX)
74    write_flags |= base::File::FLAG_ASYNC;
75#endif
76
77    return scoped_ptr<NativeProcessLauncher>(new FakeLauncher(
78        read_pipe.Pass(),
79        base::File(write_file, write_flags)));
80  }
81
82  virtual void Launch(const GURL& origin,
83                      const std::string& native_host_name,
84                      LaunchedCallback callback) const OVERRIDE {
85    callback.Run(NativeProcessLauncher::RESULT_SUCCESS,
86                 base::kNullProcessHandle,
87                 read_file_.Pass(), write_file_.Pass());
88  }
89
90 private:
91  mutable base::File read_file_;
92  mutable base::File write_file_;
93};
94
95class NativeMessagingTest : public ::testing::Test,
96                            public NativeMessageProcessHost::Client,
97                            public base::SupportsWeakPtr<NativeMessagingTest> {
98 protected:
99  NativeMessagingTest()
100      : current_channel_(chrome::VersionInfo::CHANNEL_DEV),
101        thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
102        channel_closed_(false) {}
103
104  virtual void SetUp() OVERRIDE {
105    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
106  }
107
108  virtual void TearDown() OVERRIDE {
109    if (native_message_process_host_.get()) {
110      BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE,
111                                native_message_process_host_.release());
112    }
113    base::RunLoop().RunUntilIdle();
114  }
115
116  virtual void PostMessageFromNativeProcess(
117      int port_id,
118      const std::string& message) OVERRIDE  {
119    last_message_ = message;
120
121    // Parse the message.
122    base::Value* parsed = base::JSONReader::Read(message);
123    base::DictionaryValue* dict_value;
124    if (parsed && parsed->GetAsDictionary(&dict_value)) {
125      last_message_parsed_.reset(dict_value);
126    } else {
127      LOG(ERROR) << "Failed to parse " << message;
128      last_message_parsed_.reset();
129      delete parsed;
130    }
131
132    if (run_loop_)
133      run_loop_->Quit();
134  }
135
136  virtual void CloseChannel(int port_id,
137                            const std::string& error_message) OVERRIDE {
138    channel_closed_ = true;
139    if (run_loop_)
140      run_loop_->Quit();
141  }
142
143 protected:
144  std::string FormatMessage(const std::string& message) {
145    uint32_t length = message.length();
146    return std::string(reinterpret_cast<char*>(&length), 4).append(message);
147  }
148
149  base::FilePath CreateTempFileWithMessage(const std::string& message) {
150    base::FilePath filename;
151    if (!base::CreateTemporaryFileInDir(temp_dir_.path(), &filename))
152      return base::FilePath();
153
154    std::string message_with_header = FormatMessage(message);
155    int bytes_written = base::WriteFile(
156        filename, message_with_header.data(), message_with_header.size());
157    if (bytes_written < 0 ||
158        (message_with_header.size() != static_cast<size_t>(bytes_written))) {
159      return base::FilePath();
160    }
161    return filename;
162  }
163
164  base::ScopedTempDir temp_dir_;
165  // Force the channel to be dev.
166  ScopedCurrentChannel current_channel_;
167  scoped_ptr<NativeMessageProcessHost> native_message_process_host_;
168  scoped_ptr<base::RunLoop> run_loop_;
169  content::TestBrowserThreadBundle thread_bundle_;
170  std::string last_message_;
171  scoped_ptr<base::DictionaryValue> last_message_parsed_;
172  bool channel_closed_;
173};
174
175// Read a single message from a local file.
176TEST_F(NativeMessagingTest, SingleSendMessageRead) {
177  base::FilePath temp_output_file = temp_dir_.path().AppendASCII("output");
178  base::FilePath temp_input_file = CreateTempFileWithMessage(kTestMessage);
179  ASSERT_FALSE(temp_input_file.empty());
180
181  scoped_ptr<NativeProcessLauncher> launcher =
182      FakeLauncher::Create(temp_input_file, temp_output_file).Pass();
183  native_message_process_host_ = NativeMessageProcessHost::CreateWithLauncher(
184      AsWeakPtr(), ScopedTestNativeMessagingHost::kExtensionId, "empty_app.py",
185      0, launcher.Pass());
186  ASSERT_TRUE(native_message_process_host_.get());
187  run_loop_.reset(new base::RunLoop());
188  run_loop_->RunUntilIdle();
189
190  if (last_message_.empty()) {
191    run_loop_.reset(new base::RunLoop());
192    native_message_process_host_->ReadNowForTesting();
193    run_loop_->Run();
194  }
195  EXPECT_EQ(kTestMessage, last_message_);
196}
197
198// Tests sending a single message. The message should get written to
199// |temp_file| and should match the contents of single_message_request.msg.
200TEST_F(NativeMessagingTest, SingleSendMessageWrite) {
201  base::FilePath temp_output_file = temp_dir_.path().AppendASCII("output");
202
203  base::File read_file;
204#if defined(OS_WIN)
205  base::string16 pipe_name = base::StringPrintf(
206      L"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", base::RandUint64());
207  base::File write_handle(
208      CreateNamedPipeW(pipe_name.c_str(),
209                       PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED |
210                           FILE_FLAG_FIRST_PIPE_INSTANCE,
211                       PIPE_TYPE_BYTE, 1, 0, 0, 5000, NULL));
212  ASSERT_TRUE(write_handle.IsValid());
213  base::File read_handle(
214      CreateFileW(pipe_name.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING,
215                  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL));
216  ASSERT_TRUE(read_handle.IsValid());
217
218  read_file = read_handle.Pass();
219#else  // defined(OS_WIN)
220  base::PlatformFile pipe_handles[2];
221  ASSERT_EQ(0, pipe(pipe_handles));
222  read_file = base::File(pipe_handles[0]);
223  base::File write_file(pipe_handles[1]);
224#endif  // !defined(OS_WIN)
225
226  scoped_ptr<NativeProcessLauncher> launcher =
227      FakeLauncher::CreateWithPipeInput(read_file.Pass(),
228                                        temp_output_file).Pass();
229  native_message_process_host_ = NativeMessageProcessHost::CreateWithLauncher(
230      AsWeakPtr(), ScopedTestNativeMessagingHost::kExtensionId, "empty_app.py",
231      0, launcher.Pass());
232  ASSERT_TRUE(native_message_process_host_.get());
233  base::RunLoop().RunUntilIdle();
234
235  native_message_process_host_->Send(kTestMessage);
236  base::RunLoop().RunUntilIdle();
237
238  std::string output;
239  base::TimeTicks start_time = base::TimeTicks::Now();
240  while (base::TimeTicks::Now() - start_time < TestTimeouts::action_timeout()) {
241    ASSERT_TRUE(base::ReadFileToString(temp_output_file, &output));
242    if (!output.empty())
243      break;
244    base::PlatformThread::YieldCurrentThread();
245  }
246
247  EXPECT_EQ(FormatMessage(kTestMessage), output);
248}
249
250// Test send message with a real client. The client just echo's back the text
251// it received.
252TEST_F(NativeMessagingTest, EchoConnect) {
253  ScopedTestNativeMessagingHost test_host;
254  ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
255
256  native_message_process_host_ = NativeMessageProcessHost::Create(
257      NULL, AsWeakPtr(), ScopedTestNativeMessagingHost::kExtensionId,
258      ScopedTestNativeMessagingHost::kHostName, 0, false);
259  ASSERT_TRUE(native_message_process_host_.get());
260
261  native_message_process_host_->Send("{\"text\": \"Hello.\"}");
262  run_loop_.reset(new base::RunLoop());
263  run_loop_->Run();
264  ASSERT_FALSE(last_message_.empty());
265  ASSERT_TRUE(last_message_parsed_);
266
267  std::string expected_url = std::string("chrome-extension://") +
268      ScopedTestNativeMessagingHost::kExtensionId + "/";
269  int id;
270  EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
271  EXPECT_EQ(1, id);
272  std::string text;
273  EXPECT_TRUE(last_message_parsed_->GetString("echo.text", &text));
274  EXPECT_EQ("Hello.", text);
275  std::string url;
276  EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url));
277  EXPECT_EQ(expected_url, url);
278
279  native_message_process_host_->Send("{\"foo\": \"bar\"}");
280  run_loop_.reset(new base::RunLoop());
281  run_loop_->Run();
282  EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
283  EXPECT_EQ(2, id);
284  EXPECT_TRUE(last_message_parsed_->GetString("echo.foo", &text));
285  EXPECT_EQ("bar", text);
286  EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url));
287  EXPECT_EQ(expected_url, url);
288}
289
290TEST_F(NativeMessagingTest, UserLevel) {
291  ScopedTestNativeMessagingHost test_host;
292  ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
293
294  native_message_process_host_ = NativeMessageProcessHost::Create(
295      NULL, AsWeakPtr(), ScopedTestNativeMessagingHost::kExtensionId,
296      ScopedTestNativeMessagingHost::kHostName, 0, true);
297  ASSERT_TRUE(native_message_process_host_.get());
298
299  native_message_process_host_->Send("{\"text\": \"Hello.\"}");
300  run_loop_.reset(new base::RunLoop());
301  run_loop_->Run();
302  ASSERT_FALSE(last_message_.empty());
303  ASSERT_TRUE(last_message_parsed_);
304}
305
306TEST_F(NativeMessagingTest, DisallowUserLevel) {
307  ScopedTestNativeMessagingHost test_host;
308  ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
309
310  native_message_process_host_ = NativeMessageProcessHost::Create(
311      NULL, AsWeakPtr(), ScopedTestNativeMessagingHost::kExtensionId,
312      ScopedTestNativeMessagingHost::kHostName, 0, false);
313  ASSERT_TRUE(native_message_process_host_.get());
314  run_loop_.reset(new base::RunLoop());
315  run_loop_->Run();
316
317  // The host should fail to start.
318  ASSERT_TRUE(channel_closed_);
319}
320
321}  // namespace extensions
322