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.
5 #include "chrome/browser/extensions/api/messaging/native_message_process_host.h"
8 #include "base/files/file.h"
9 #include "base/files/file_path.h"
10 #include "base/files/file_util.h"
11 #include "base/files/scoped_file.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/json/json_reader.h"
14 #include "base/macros.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/rand_util.h"
18 #include "base/run_loop.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/test/test_timeouts.h"
21 #include "base/threading/platform_thread.h"
22 #include "base/threading/sequenced_worker_pool.h"
23 #include "base/time/time.h"
24 #include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
25 #include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
26 #include "chrome/common/extensions/features/feature_channel.h"
27 #include "components/version_info/version_info.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/test/test_browser_thread_bundle.h"
30 #include "extensions/common/extension.h"
31 #include "testing/gtest/include/gtest/gtest.h"
35 #include "base/win/scoped_handle.h"
40 using content::BrowserThread
;
44 const char kTestMessage
[] = "{\"text\": \"Hello.\"}";
48 namespace extensions
{
50 class FakeLauncher
: public NativeProcessLauncher
{
52 FakeLauncher(base::File read_file
, base::File write_file
)
53 : read_file_(read_file
.Pass()),
54 write_file_(write_file
.Pass()) {
57 static scoped_ptr
<NativeProcessLauncher
> Create(base::FilePath read_file
,
58 base::FilePath write_file
) {
59 int read_flags
= base::File::FLAG_OPEN
| base::File::FLAG_READ
;
60 int write_flags
= base::File::FLAG_CREATE
| base::File::FLAG_WRITE
;
61 #if !defined(OS_POSIX)
62 read_flags
|= base::File::FLAG_ASYNC
;
63 write_flags
|= base::File::FLAG_ASYNC
;
65 return scoped_ptr
<NativeProcessLauncher
>(new FakeLauncher(
66 base::File(read_file
, read_flags
),
67 base::File(write_file
, write_flags
)));
70 static scoped_ptr
<NativeProcessLauncher
> CreateWithPipeInput(
72 base::FilePath write_file
) {
73 int write_flags
= base::File::FLAG_CREATE
| base::File::FLAG_WRITE
;
74 #if !defined(OS_POSIX)
75 write_flags
|= base::File::FLAG_ASYNC
;
78 return scoped_ptr
<NativeProcessLauncher
>(new FakeLauncher(
80 base::File(write_file
, write_flags
)));
83 void Launch(const GURL
& origin
,
84 const std::string
& native_host_name
,
85 const LaunchedCallback
& callback
) const override
{
86 callback
.Run(NativeProcessLauncher::RESULT_SUCCESS
,
87 base::Process(), read_file_
.Pass(), write_file_
.Pass());
91 mutable base::File read_file_
;
92 mutable base::File write_file_
;
95 class NativeMessagingTest
: public ::testing::Test
,
96 public NativeMessageHost::Client
,
97 public base::SupportsWeakPtr
<NativeMessagingTest
> {
100 : current_channel_(version_info::Channel::DEV
),
101 thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP
),
102 channel_closed_(false) {}
104 void SetUp() override
{ ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir()); }
106 void TearDown() override
{
107 if (native_message_host_
.get()) {
108 BrowserThread::DeleteSoon(
109 BrowserThread::IO
, FROM_HERE
, native_message_host_
.release());
111 base::RunLoop().RunUntilIdle();
114 void PostMessageFromNativeHost(const std::string
& message
) override
{
115 last_message_
= message
;
117 // Parse the message.
118 scoped_ptr
<base::Value
> parsed
= base::JSONReader::Read(message
);
119 base::DictionaryValue
* dict_value
;
120 if (parsed
.get() && parsed
->GetAsDictionary(&dict_value
)) {
121 ignore_result(parsed
.release());
122 last_message_parsed_
.reset(dict_value
);
124 LOG(ERROR
) << "Failed to parse " << message
;
125 last_message_parsed_
.reset();
132 void CloseChannel(const std::string
& error_message
) override
{
133 channel_closed_
= true;
139 std::string
FormatMessage(const std::string
& message
) {
140 uint32_t length
= message
.length();
141 return std::string(reinterpret_cast<char*>(&length
), 4).append(message
);
144 base::FilePath
CreateTempFileWithMessage(const std::string
& message
) {
145 base::FilePath filename
;
146 if (!base::CreateTemporaryFileInDir(temp_dir_
.path(), &filename
))
147 return base::FilePath();
149 std::string message_with_header
= FormatMessage(message
);
150 int bytes_written
= base::WriteFile(
151 filename
, message_with_header
.data(), message_with_header
.size());
152 if (bytes_written
< 0 ||
153 (message_with_header
.size() != static_cast<size_t>(bytes_written
))) {
154 return base::FilePath();
159 base::ScopedTempDir temp_dir_
;
160 // Force the channel to be dev.
161 ScopedCurrentChannel current_channel_
;
162 scoped_ptr
<NativeMessageHost
> native_message_host_
;
163 scoped_ptr
<base::RunLoop
> run_loop_
;
164 content::TestBrowserThreadBundle thread_bundle_
;
165 std::string last_message_
;
166 scoped_ptr
<base::DictionaryValue
> last_message_parsed_
;
167 bool channel_closed_
;
170 // Read a single message from a local file.
171 TEST_F(NativeMessagingTest
, SingleSendMessageRead
) {
172 base::FilePath temp_output_file
= temp_dir_
.path().AppendASCII("output");
173 base::FilePath temp_input_file
= CreateTempFileWithMessage(kTestMessage
);
174 ASSERT_FALSE(temp_input_file
.empty());
176 scoped_ptr
<NativeProcessLauncher
> launcher
=
177 FakeLauncher::Create(temp_input_file
, temp_output_file
).Pass();
178 native_message_host_
= NativeMessageProcessHost::CreateWithLauncher(
179 ScopedTestNativeMessagingHost::kExtensionId
,
182 native_message_host_
->Start(this);
183 ASSERT_TRUE(native_message_host_
.get());
184 run_loop_
.reset(new base::RunLoop());
185 run_loop_
->RunUntilIdle();
187 if (last_message_
.empty()) {
188 run_loop_
.reset(new base::RunLoop());
189 scoped_ptr
<NativeMessageProcessHost
> native_message_process_host_(
190 static_cast<NativeMessageProcessHost
*>(native_message_host_
.release()));
191 native_message_process_host_
->ReadNowForTesting();
194 EXPECT_EQ(kTestMessage
, last_message_
);
197 // Tests sending a single message. The message should get written to
198 // |temp_file| and should match the contents of single_message_request.msg.
199 TEST_F(NativeMessagingTest
, SingleSendMessageWrite
) {
200 base::FilePath temp_output_file
= temp_dir_
.path().AppendASCII("output");
202 base::File read_file
;
204 base::string16 pipe_name
= base::StringPrintf(
205 L
"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", base::RandUint64());
206 base::File write_handle
= base::File::CreateForAsyncHandle(
207 CreateNamedPipeW(pipe_name
.c_str(),
208 PIPE_ACCESS_OUTBOUND
| FILE_FLAG_OVERLAPPED
|
209 FILE_FLAG_FIRST_PIPE_INSTANCE
,
210 PIPE_TYPE_BYTE
, 1, 0, 0, 5000, NULL
));
211 ASSERT_TRUE(write_handle
.IsValid());
212 base::File read_handle
= base::File::CreateForAsyncHandle(
213 CreateFileW(pipe_name
.c_str(), GENERIC_READ
, 0, NULL
, OPEN_EXISTING
,
214 FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_OVERLAPPED
, NULL
));
215 ASSERT_TRUE(read_handle
.IsValid());
217 read_file
= read_handle
.Pass();
218 #else // defined(OS_WIN)
219 base::PlatformFile pipe_handles
[2];
220 ASSERT_EQ(0, pipe(pipe_handles
));
221 read_file
= base::File(pipe_handles
[0]);
222 base::File
write_file(pipe_handles
[1]);
223 #endif // !defined(OS_WIN)
225 scoped_ptr
<NativeProcessLauncher
> launcher
=
226 FakeLauncher::CreateWithPipeInput(read_file
.Pass(),
227 temp_output_file
).Pass();
228 native_message_host_
= NativeMessageProcessHost::CreateWithLauncher(
229 ScopedTestNativeMessagingHost::kExtensionId
,
232 native_message_host_
->Start(this);
233 ASSERT_TRUE(native_message_host_
.get());
234 base::RunLoop().RunUntilIdle();
236 native_message_host_
->OnMessage(kTestMessage
);
237 base::RunLoop().RunUntilIdle();
240 base::TimeTicks start_time
= base::TimeTicks::Now();
241 while (base::TimeTicks::Now() - start_time
< TestTimeouts::action_timeout()) {
242 ASSERT_TRUE(base::ReadFileToString(temp_output_file
, &output
));
245 base::PlatformThread::YieldCurrentThread();
248 EXPECT_EQ(FormatMessage(kTestMessage
), output
);
251 // Test send message with a real client. The client just echo's back the text
253 TEST_F(NativeMessagingTest
, EchoConnect
) {
254 ScopedTestNativeMessagingHost test_host
;
255 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(false));
256 std::string error_message
;
257 native_message_host_
= NativeMessageProcessHost::Create(
259 ScopedTestNativeMessagingHost::kExtensionId
,
260 ScopedTestNativeMessagingHost::kHostName
,
263 native_message_host_
->Start(this);
264 ASSERT_TRUE(native_message_host_
.get());
266 native_message_host_
->OnMessage("{\"text\": \"Hello.\"}");
267 run_loop_
.reset(new base::RunLoop());
269 ASSERT_FALSE(last_message_
.empty());
270 ASSERT_TRUE(last_message_parsed_
);
272 std::string expected_url
= std::string("chrome-extension://") +
273 ScopedTestNativeMessagingHost::kExtensionId
+ "/";
275 EXPECT_TRUE(last_message_parsed_
->GetInteger("id", &id
));
278 EXPECT_TRUE(last_message_parsed_
->GetString("echo.text", &text
));
279 EXPECT_EQ("Hello.", text
);
281 EXPECT_TRUE(last_message_parsed_
->GetString("caller_url", &url
));
282 EXPECT_EQ(expected_url
, url
);
284 native_message_host_
->OnMessage("{\"foo\": \"bar\"}");
285 run_loop_
.reset(new base::RunLoop());
287 EXPECT_TRUE(last_message_parsed_
->GetInteger("id", &id
));
289 EXPECT_TRUE(last_message_parsed_
->GetString("echo.foo", &text
));
290 EXPECT_EQ("bar", text
);
291 EXPECT_TRUE(last_message_parsed_
->GetString("caller_url", &url
));
292 EXPECT_EQ(expected_url
, url
);
295 TEST_F(NativeMessagingTest
, UserLevel
) {
296 ScopedTestNativeMessagingHost test_host
;
297 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(true));
299 std::string error_message
;
300 native_message_host_
= NativeMessageProcessHost::Create(
302 ScopedTestNativeMessagingHost::kExtensionId
,
303 ScopedTestNativeMessagingHost::kHostName
,
306 native_message_host_
->Start(this);
307 ASSERT_TRUE(native_message_host_
.get());
309 native_message_host_
->OnMessage("{\"text\": \"Hello.\"}");
310 run_loop_
.reset(new base::RunLoop());
312 ASSERT_FALSE(last_message_
.empty());
313 ASSERT_TRUE(last_message_parsed_
);
316 TEST_F(NativeMessagingTest
, DisallowUserLevel
) {
317 ScopedTestNativeMessagingHost test_host
;
318 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(true));
320 std::string error_message
;
321 native_message_host_
= NativeMessageProcessHost::Create(
323 ScopedTestNativeMessagingHost::kExtensionId
,
324 ScopedTestNativeMessagingHost::kHostName
,
327 native_message_host_
->Start(this);
328 ASSERT_TRUE(native_message_host_
.get());
329 run_loop_
.reset(new base::RunLoop());
332 // The host should fail to start.
333 ASSERT_TRUE(channel_closed_
);
336 } // namespace extensions