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.
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/message_loop/message_loop.h"
14 #include "base/rand_util.h"
15 #include "base/run_loop.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/test/test_timeouts.h"
18 #include "base/threading/platform_thread.h"
19 #include "base/threading/sequenced_worker_pool.h"
20 #include "base/time/time.h"
21 #include "chrome/browser/extensions/api/messaging/native_message_process_host.h"
22 #include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
23 #include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
24 #include "chrome/common/chrome_version_info.h"
25 #include "chrome/common/extensions/features/feature_channel.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "content/public/test/test_browser_thread_bundle.h"
28 #include "extensions/common/extension.h"
29 #include "testing/gtest/include/gtest/gtest.h"
33 #include "base/win/scoped_handle.h"
38 using content::BrowserThread
;
42 const char kTestMessage
[] = "{\"text\": \"Hello.\"}";
46 namespace extensions
{
48 class FakeLauncher
: public NativeProcessLauncher
{
50 FakeLauncher(base::File read_file
, base::File write_file
)
51 : read_file_(read_file
.Pass()),
52 write_file_(write_file
.Pass()) {
55 static scoped_ptr
<NativeProcessLauncher
> Create(base::FilePath read_file
,
56 base::FilePath write_file
) {
57 int read_flags
= base::File::FLAG_OPEN
| base::File::FLAG_READ
;
58 int write_flags
= base::File::FLAG_CREATE
| base::File::FLAG_WRITE
;
59 #if !defined(OS_POSIX)
60 read_flags
|= base::File::FLAG_ASYNC
;
61 write_flags
|= base::File::FLAG_ASYNC
;
63 return scoped_ptr
<NativeProcessLauncher
>(new FakeLauncher(
64 base::File(read_file
, read_flags
),
65 base::File(write_file
, write_flags
)));
68 static scoped_ptr
<NativeProcessLauncher
> CreateWithPipeInput(
70 base::FilePath write_file
) {
71 int write_flags
= base::File::FLAG_CREATE
| base::File::FLAG_WRITE
;
72 #if !defined(OS_POSIX)
73 write_flags
|= base::File::FLAG_ASYNC
;
76 return scoped_ptr
<NativeProcessLauncher
>(new FakeLauncher(
78 base::File(write_file
, write_flags
)));
81 void Launch(const GURL
& origin
,
82 const std::string
& native_host_name
,
83 const LaunchedCallback
& callback
) const override
{
84 callback
.Run(NativeProcessLauncher::RESULT_SUCCESS
,
85 base::Process(), read_file_
.Pass(), write_file_
.Pass());
89 mutable base::File read_file_
;
90 mutable base::File write_file_
;
93 class NativeMessagingTest
: public ::testing::Test
,
94 public NativeMessageHost::Client
,
95 public base::SupportsWeakPtr
<NativeMessagingTest
> {
98 : current_channel_(chrome::VersionInfo::CHANNEL_DEV
),
99 thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP
),
100 channel_closed_(false) {}
102 void SetUp() override
{ ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir()); }
104 void TearDown() override
{
105 if (native_message_host_
.get()) {
106 BrowserThread::DeleteSoon(
107 BrowserThread::IO
, FROM_HERE
, native_message_host_
.release());
109 base::RunLoop().RunUntilIdle();
112 void PostMessageFromNativeHost(const std::string
& message
) override
{
113 last_message_
= message
;
115 // Parse the message.
116 base::Value
* parsed
= base::JSONReader::DeprecatedRead(message
);
117 base::DictionaryValue
* dict_value
;
118 if (parsed
&& parsed
->GetAsDictionary(&dict_value
)) {
119 last_message_parsed_
.reset(dict_value
);
121 LOG(ERROR
) << "Failed to parse " << message
;
122 last_message_parsed_
.reset();
130 void CloseChannel(const std::string
& error_message
) override
{
131 channel_closed_
= true;
137 std::string
FormatMessage(const std::string
& message
) {
138 uint32_t length
= message
.length();
139 return std::string(reinterpret_cast<char*>(&length
), 4).append(message
);
142 base::FilePath
CreateTempFileWithMessage(const std::string
& message
) {
143 base::FilePath filename
;
144 if (!base::CreateTemporaryFileInDir(temp_dir_
.path(), &filename
))
145 return base::FilePath();
147 std::string message_with_header
= FormatMessage(message
);
148 int bytes_written
= base::WriteFile(
149 filename
, message_with_header
.data(), message_with_header
.size());
150 if (bytes_written
< 0 ||
151 (message_with_header
.size() != static_cast<size_t>(bytes_written
))) {
152 return base::FilePath();
157 base::ScopedTempDir temp_dir_
;
158 // Force the channel to be dev.
159 ScopedCurrentChannel current_channel_
;
160 scoped_ptr
<NativeMessageHost
> native_message_host_
;
161 scoped_ptr
<base::RunLoop
> run_loop_
;
162 content::TestBrowserThreadBundle thread_bundle_
;
163 std::string last_message_
;
164 scoped_ptr
<base::DictionaryValue
> last_message_parsed_
;
165 bool channel_closed_
;
168 // Read a single message from a local file.
169 TEST_F(NativeMessagingTest
, SingleSendMessageRead
) {
170 base::FilePath temp_output_file
= temp_dir_
.path().AppendASCII("output");
171 base::FilePath temp_input_file
= CreateTempFileWithMessage(kTestMessage
);
172 ASSERT_FALSE(temp_input_file
.empty());
174 scoped_ptr
<NativeProcessLauncher
> launcher
=
175 FakeLauncher::Create(temp_input_file
, temp_output_file
).Pass();
176 native_message_host_
= NativeMessageProcessHost::CreateWithLauncher(
177 ScopedTestNativeMessagingHost::kExtensionId
,
180 native_message_host_
->Start(this);
181 ASSERT_TRUE(native_message_host_
.get());
182 run_loop_
.reset(new base::RunLoop());
183 run_loop_
->RunUntilIdle();
185 if (last_message_
.empty()) {
186 run_loop_
.reset(new base::RunLoop());
187 scoped_ptr
<NativeMessageProcessHost
> native_message_process_host_(
188 static_cast<NativeMessageProcessHost
*>(native_message_host_
.release()));
189 native_message_process_host_
->ReadNowForTesting();
192 EXPECT_EQ(kTestMessage
, last_message_
);
195 // Tests sending a single message. The message should get written to
196 // |temp_file| and should match the contents of single_message_request.msg.
197 TEST_F(NativeMessagingTest
, SingleSendMessageWrite
) {
198 base::FilePath temp_output_file
= temp_dir_
.path().AppendASCII("output");
200 base::File read_file
;
202 base::string16 pipe_name
= base::StringPrintf(
203 L
"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", base::RandUint64());
204 base::File write_handle
= base::File::CreateForAsyncHandle(
205 CreateNamedPipeW(pipe_name
.c_str(),
206 PIPE_ACCESS_OUTBOUND
| FILE_FLAG_OVERLAPPED
|
207 FILE_FLAG_FIRST_PIPE_INSTANCE
,
208 PIPE_TYPE_BYTE
, 1, 0, 0, 5000, NULL
));
209 ASSERT_TRUE(write_handle
.IsValid());
210 base::File read_handle
= base::File::CreateForAsyncHandle(
211 CreateFileW(pipe_name
.c_str(), GENERIC_READ
, 0, NULL
, OPEN_EXISTING
,
212 FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_OVERLAPPED
, NULL
));
213 ASSERT_TRUE(read_handle
.IsValid());
215 read_file
= read_handle
.Pass();
216 #else // defined(OS_WIN)
217 base::PlatformFile pipe_handles
[2];
218 ASSERT_EQ(0, pipe(pipe_handles
));
219 read_file
= base::File(pipe_handles
[0]);
220 base::File
write_file(pipe_handles
[1]);
221 #endif // !defined(OS_WIN)
223 scoped_ptr
<NativeProcessLauncher
> launcher
=
224 FakeLauncher::CreateWithPipeInput(read_file
.Pass(),
225 temp_output_file
).Pass();
226 native_message_host_
= NativeMessageProcessHost::CreateWithLauncher(
227 ScopedTestNativeMessagingHost::kExtensionId
,
230 native_message_host_
->Start(this);
231 ASSERT_TRUE(native_message_host_
.get());
232 base::RunLoop().RunUntilIdle();
234 native_message_host_
->OnMessage(kTestMessage
);
235 base::RunLoop().RunUntilIdle();
238 base::TimeTicks start_time
= base::TimeTicks::Now();
239 while (base::TimeTicks::Now() - start_time
< TestTimeouts::action_timeout()) {
240 ASSERT_TRUE(base::ReadFileToString(temp_output_file
, &output
));
243 base::PlatformThread::YieldCurrentThread();
246 EXPECT_EQ(FormatMessage(kTestMessage
), output
);
249 // Test send message with a real client. The client just echo's back the text
251 TEST_F(NativeMessagingTest
, EchoConnect
) {
252 ScopedTestNativeMessagingHost test_host
;
253 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(false));
254 std::string error_message
;
255 native_message_host_
= NativeMessageProcessHost::Create(
257 ScopedTestNativeMessagingHost::kExtensionId
,
258 ScopedTestNativeMessagingHost::kHostName
,
261 native_message_host_
->Start(this);
262 ASSERT_TRUE(native_message_host_
.get());
264 native_message_host_
->OnMessage("{\"text\": \"Hello.\"}");
265 run_loop_
.reset(new base::RunLoop());
267 ASSERT_FALSE(last_message_
.empty());
268 ASSERT_TRUE(last_message_parsed_
);
270 std::string expected_url
= std::string("chrome-extension://") +
271 ScopedTestNativeMessagingHost::kExtensionId
+ "/";
273 EXPECT_TRUE(last_message_parsed_
->GetInteger("id", &id
));
276 EXPECT_TRUE(last_message_parsed_
->GetString("echo.text", &text
));
277 EXPECT_EQ("Hello.", text
);
279 EXPECT_TRUE(last_message_parsed_
->GetString("caller_url", &url
));
280 EXPECT_EQ(expected_url
, url
);
282 native_message_host_
->OnMessage("{\"foo\": \"bar\"}");
283 run_loop_
.reset(new base::RunLoop());
285 EXPECT_TRUE(last_message_parsed_
->GetInteger("id", &id
));
287 EXPECT_TRUE(last_message_parsed_
->GetString("echo.foo", &text
));
288 EXPECT_EQ("bar", text
);
289 EXPECT_TRUE(last_message_parsed_
->GetString("caller_url", &url
));
290 EXPECT_EQ(expected_url
, url
);
293 TEST_F(NativeMessagingTest
, UserLevel
) {
294 ScopedTestNativeMessagingHost test_host
;
295 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(true));
297 std::string error_message
;
298 native_message_host_
= NativeMessageProcessHost::Create(
300 ScopedTestNativeMessagingHost::kExtensionId
,
301 ScopedTestNativeMessagingHost::kHostName
,
304 native_message_host_
->Start(this);
305 ASSERT_TRUE(native_message_host_
.get());
307 native_message_host_
->OnMessage("{\"text\": \"Hello.\"}");
308 run_loop_
.reset(new base::RunLoop());
310 ASSERT_FALSE(last_message_
.empty());
311 ASSERT_TRUE(last_message_parsed_
);
314 TEST_F(NativeMessagingTest
, DisallowUserLevel
) {
315 ScopedTestNativeMessagingHost test_host
;
316 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(true));
318 std::string error_message
;
319 native_message_host_
= NativeMessageProcessHost::Create(
321 ScopedTestNativeMessagingHost::kExtensionId
,
322 ScopedTestNativeMessagingHost::kHostName
,
325 native_message_host_
->Start(this);
326 ASSERT_TRUE(native_message_host_
.get());
327 run_loop_
.reset(new base::RunLoop());
330 // The host should fail to start.
331 ASSERT_TRUE(channel_closed_
);
334 } // namespace extensions