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/file_util.h"
7 #include "base/files/file.h"
8 #include "base/files/file_path.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"
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 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());
91 mutable base::File read_file_
;
92 mutable base::File write_file_
;
95 class NativeMessagingTest
: public ::testing::Test
,
96 public NativeMessageProcessHost::Client
,
97 public base::SupportsWeakPtr
<NativeMessagingTest
> {
100 : current_channel_(chrome::VersionInfo::CHANNEL_DEV
),
101 thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP
),
102 channel_closed_(false) {}
104 virtual void SetUp() OVERRIDE
{
105 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
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());
113 base::RunLoop().RunUntilIdle();
116 virtual void PostMessageFromNativeProcess(
118 const std::string
& message
) OVERRIDE
{
119 last_message_
= message
;
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
);
127 LOG(ERROR
) << "Failed to parse " << message
;
128 last_message_parsed_
.reset();
136 virtual void CloseChannel(int port_id
,
137 const std::string
& error_message
) OVERRIDE
{
138 channel_closed_
= true;
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
);
149 base::FilePath
CreateTempFileWithMessage(const std::string
& message
) {
150 base::FilePath filename
;
151 if (!base::CreateTemporaryFileInDir(temp_dir_
.path(), &filename
))
152 return base::FilePath();
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();
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_
;
175 // Read a single message from a local file.
176 TEST_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());
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",
186 ASSERT_TRUE(native_message_process_host_
.get());
187 run_loop_
.reset(new base::RunLoop());
188 run_loop_
->RunUntilIdle();
190 if (last_message_
.empty()) {
191 run_loop_
.reset(new base::RunLoop());
192 native_message_process_host_
->ReadNowForTesting();
195 EXPECT_EQ(kTestMessage
, last_message_
);
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.
200 TEST_F(NativeMessagingTest
, SingleSendMessageWrite
) {
201 base::FilePath temp_output_file
= temp_dir_
.path().AppendASCII("output");
203 base::File read_file
;
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());
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)
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",
232 ASSERT_TRUE(native_message_process_host_
.get());
233 base::RunLoop().RunUntilIdle();
235 native_message_process_host_
->Send(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));
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());
261 native_message_process_host_
->Send("{\"text\": \"Hello.\"}");
262 run_loop_
.reset(new base::RunLoop());
264 ASSERT_FALSE(last_message_
.empty());
265 ASSERT_TRUE(last_message_parsed_
);
267 std::string expected_url
= std::string("chrome-extension://") +
268 ScopedTestNativeMessagingHost::kExtensionId
+ "/";
270 EXPECT_TRUE(last_message_parsed_
->GetInteger("id", &id
));
273 EXPECT_TRUE(last_message_parsed_
->GetString("echo.text", &text
));
274 EXPECT_EQ("Hello.", text
);
276 EXPECT_TRUE(last_message_parsed_
->GetString("caller_url", &url
));
277 EXPECT_EQ(expected_url
, url
);
279 native_message_process_host_
->Send("{\"foo\": \"bar\"}");
280 run_loop_
.reset(new base::RunLoop());
282 EXPECT_TRUE(last_message_parsed_
->GetInteger("id", &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
);
290 TEST_F(NativeMessagingTest
, UserLevel
) {
291 ScopedTestNativeMessagingHost test_host
;
292 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(true));
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());
299 native_message_process_host_
->Send("{\"text\": \"Hello.\"}");
300 run_loop_
.reset(new base::RunLoop());
302 ASSERT_FALSE(last_message_
.empty());
303 ASSERT_TRUE(last_message_parsed_
);
306 TEST_F(NativeMessagingTest
, DisallowUserLevel
) {
307 ScopedTestNativeMessagingHost test_host
;
308 ASSERT_NO_FATAL_FAILURE(test_host
.RegisterTestHost(true));
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());
317 // The host should fail to start.
318 ASSERT_TRUE(channel_closed_
);
321 } // namespace extensions