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/memory/scoped_ptr.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/rand_util.h"
17 #include "base/run_loop.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/test/test_timeouts.h"
20 #include "base/threading/platform_thread.h"
21 #include "base/threading/sequenced_worker_pool.h"
22 #include "base/time/time.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/extensions/features/feature_channel.h"
26 #include "components/version_info/version_info.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"
34 #include "base/win/scoped_handle.h"
39 using content::BrowserThread
;
43 const char kTestMessage
[] = "{\"text\": \"Hello.\"}";
47 namespace extensions
{
49 class FakeLauncher
: public NativeProcessLauncher
{
51 FakeLauncher(base::File read_file
, base::File write_file
)
52 : read_file_(read_file
.Pass()),
53 write_file_(write_file
.Pass()) {
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
;
64 return scoped_ptr
<NativeProcessLauncher
>(new FakeLauncher(
65 base::File(read_file
, read_flags
),
66 base::File(write_file
, write_flags
)));
69 static scoped_ptr
<NativeProcessLauncher
> CreateWithPipeInput(
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
;
77 return scoped_ptr
<NativeProcessLauncher
>(new FakeLauncher(
79 base::File(write_file
, write_flags
)));
82 void Launch(const GURL
& origin
,
83 const std::string
& native_host_name
,
84 const LaunchedCallback
& callback
) const override
{
85 callback
.Run(NativeProcessLauncher::RESULT_SUCCESS
,
86 base::Process(), read_file_
.Pass(), write_file_
.Pass());
90 mutable base::File read_file_
;
91 mutable base::File write_file_
;
94 class NativeMessagingTest
: public ::testing::Test
,
95 public NativeMessageHost::Client
,
96 public base::SupportsWeakPtr
<NativeMessagingTest
> {
99 : current_channel_(version_info::Channel::DEV
),
100 thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP
),
101 channel_closed_(false) {}
103 void SetUp() override
{ ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir()); }
105 void TearDown() override
{
106 if (native_message_host_
.get()) {
107 BrowserThread::DeleteSoon(
108 BrowserThread::IO
, FROM_HERE
, native_message_host_
.release());
110 base::RunLoop().RunUntilIdle();
113 void PostMessageFromNativeHost(const std::string
& message
) override
{
114 last_message_
= message
;
116 // Parse the message.
117 base::Value
* parsed
= base::JSONReader::DeprecatedRead(message
);
118 base::DictionaryValue
* dict_value
;
119 if (parsed
&& parsed
->GetAsDictionary(&dict_value
)) {
120 last_message_parsed_
.reset(dict_value
);
122 LOG(ERROR
) << "Failed to parse " << message
;
123 last_message_parsed_
.reset();
131 void CloseChannel(const std::string
& error_message
) override
{
132 channel_closed_
= true;
138 std::string
FormatMessage(const std::string
& message
) {
139 uint32_t length
= message
.length();
140 return std::string(reinterpret_cast<char*>(&length
), 4).append(message
);
143 base::FilePath
CreateTempFileWithMessage(const std::string
& message
) {
144 base::FilePath filename
;
145 if (!base::CreateTemporaryFileInDir(temp_dir_
.path(), &filename
))
146 return base::FilePath();
148 std::string message_with_header
= FormatMessage(message
);
149 int bytes_written
= base::WriteFile(
150 filename
, message_with_header
.data(), message_with_header
.size());
151 if (bytes_written
< 0 ||
152 (message_with_header
.size() != static_cast<size_t>(bytes_written
))) {
153 return base::FilePath();
158 base::ScopedTempDir temp_dir_
;
159 // Force the channel to be dev.
160 ScopedCurrentChannel current_channel_
;
161 scoped_ptr
<NativeMessageHost
> native_message_host_
;
162 scoped_ptr
<base::RunLoop
> run_loop_
;
163 content::TestBrowserThreadBundle thread_bundle_
;
164 std::string last_message_
;
165 scoped_ptr
<base::DictionaryValue
> last_message_parsed_
;
166 bool channel_closed_
;
169 // Read a single message from a local file.
170 TEST_F(NativeMessagingTest
, SingleSendMessageRead
) {
171 base::FilePath temp_output_file
= temp_dir_
.path().AppendASCII("output");
172 base::FilePath temp_input_file
= CreateTempFileWithMessage(kTestMessage
);
173 ASSERT_FALSE(temp_input_file
.empty());
175 scoped_ptr
<NativeProcessLauncher
> launcher
=
176 FakeLauncher::Create(temp_input_file
, temp_output_file
).Pass();
177 native_message_host_
= NativeMessageProcessHost::CreateWithLauncher(
178 ScopedTestNativeMessagingHost::kExtensionId
,
181 native_message_host_
->Start(this);
182 ASSERT_TRUE(native_message_host_
.get());
183 run_loop_
.reset(new base::RunLoop());
184 run_loop_
->RunUntilIdle();
186 if (last_message_
.empty()) {
187 run_loop_
.reset(new base::RunLoop());
188 scoped_ptr
<NativeMessageProcessHost
> native_message_process_host_(
189 static_cast<NativeMessageProcessHost
*>(native_message_host_
.release()));
190 native_message_process_host_
->ReadNowForTesting();
193 EXPECT_EQ(kTestMessage
, last_message_
);
196 // Tests sending a single message. The message should get written to
197 // |temp_file| and should match the contents of single_message_request.msg.
198 TEST_F(NativeMessagingTest
, SingleSendMessageWrite
) {
199 base::FilePath temp_output_file
= temp_dir_
.path().AppendASCII("output");
201 base::File read_file
;
203 base::string16 pipe_name
= base::StringPrintf(
204 L
"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", base::RandUint64());
205 base::File write_handle
= base::File::CreateForAsyncHandle(
206 CreateNamedPipeW(pipe_name
.c_str(),
207 PIPE_ACCESS_OUTBOUND
| FILE_FLAG_OVERLAPPED
|
208 FILE_FLAG_FIRST_PIPE_INSTANCE
,
209 PIPE_TYPE_BYTE
, 1, 0, 0, 5000, NULL
));
210 ASSERT_TRUE(write_handle
.IsValid());
211 base::File read_handle
= base::File::CreateForAsyncHandle(
212 CreateFileW(pipe_name
.c_str(), GENERIC_READ
, 0, NULL
, OPEN_EXISTING
,
213 FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_OVERLAPPED
, NULL
));
214 ASSERT_TRUE(read_handle
.IsValid());
216 read_file
= read_handle
.Pass();
217 #else // defined(OS_WIN)
218 base::PlatformFile pipe_handles
[2];
219 ASSERT_EQ(0, pipe(pipe_handles
));
220 read_file
= base::File(pipe_handles
[0]);
221 base::File
write_file(pipe_handles
[1]);
222 #endif // !defined(OS_WIN)
224 scoped_ptr
<NativeProcessLauncher
> launcher
=
225 FakeLauncher::CreateWithPipeInput(read_file
.Pass(),
226 temp_output_file
).Pass();
227 native_message_host_
= NativeMessageProcessHost::CreateWithLauncher(
228 ScopedTestNativeMessagingHost::kExtensionId
,
231 native_message_host_
->Start(this);
232 ASSERT_TRUE(native_message_host_
.get());
233 base::RunLoop().RunUntilIdle();
235 native_message_host_
->OnMessage(kTestMessage
);
236 base::RunLoop().RunUntilIdle();
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
));
244 base::PlatformThread::YieldCurrentThread();
247 EXPECT_EQ(FormatMessage(kTestMessage
), output
);
250 // Test send message with a real client. The client just echo's back the text
252 TEST_F(NativeMessagingTest
, EchoConnect
) {
253 ScopedTestNativeMessagingHost test_host
;
254 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(false));
255 std::string error_message
;
256 native_message_host_
= NativeMessageProcessHost::Create(
258 ScopedTestNativeMessagingHost::kExtensionId
,
259 ScopedTestNativeMessagingHost::kHostName
,
262 native_message_host_
->Start(this);
263 ASSERT_TRUE(native_message_host_
.get());
265 native_message_host_
->OnMessage("{\"text\": \"Hello.\"}");
266 run_loop_
.reset(new base::RunLoop());
268 ASSERT_FALSE(last_message_
.empty());
269 ASSERT_TRUE(last_message_parsed_
);
271 std::string expected_url
= std::string("chrome-extension://") +
272 ScopedTestNativeMessagingHost::kExtensionId
+ "/";
274 EXPECT_TRUE(last_message_parsed_
->GetInteger("id", &id
));
277 EXPECT_TRUE(last_message_parsed_
->GetString("echo.text", &text
));
278 EXPECT_EQ("Hello.", text
);
280 EXPECT_TRUE(last_message_parsed_
->GetString("caller_url", &url
));
281 EXPECT_EQ(expected_url
, url
);
283 native_message_host_
->OnMessage("{\"foo\": \"bar\"}");
284 run_loop_
.reset(new base::RunLoop());
286 EXPECT_TRUE(last_message_parsed_
->GetInteger("id", &id
));
288 EXPECT_TRUE(last_message_parsed_
->GetString("echo.foo", &text
));
289 EXPECT_EQ("bar", text
);
290 EXPECT_TRUE(last_message_parsed_
->GetString("caller_url", &url
));
291 EXPECT_EQ(expected_url
, url
);
294 TEST_F(NativeMessagingTest
, UserLevel
) {
295 ScopedTestNativeMessagingHost test_host
;
296 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(true));
298 std::string error_message
;
299 native_message_host_
= NativeMessageProcessHost::Create(
301 ScopedTestNativeMessagingHost::kExtensionId
,
302 ScopedTestNativeMessagingHost::kHostName
,
305 native_message_host_
->Start(this);
306 ASSERT_TRUE(native_message_host_
.get());
308 native_message_host_
->OnMessage("{\"text\": \"Hello.\"}");
309 run_loop_
.reset(new base::RunLoop());
311 ASSERT_FALSE(last_message_
.empty());
312 ASSERT_TRUE(last_message_parsed_
);
315 TEST_F(NativeMessagingTest
, DisallowUserLevel
) {
316 ScopedTestNativeMessagingHost test_host
;
317 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(true));
319 std::string error_message
;
320 native_message_host_
= NativeMessageProcessHost::Create(
322 ScopedTestNativeMessagingHost::kExtensionId
,
323 ScopedTestNativeMessagingHost::kHostName
,
326 native_message_host_
->Start(this);
327 ASSERT_TRUE(native_message_host_
.get());
328 run_loop_
.reset(new base::RunLoop());
331 // The host should fail to start.
332 ASSERT_TRUE(channel_closed_
);
335 } // namespace extensions