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 "chrome/browser/extensions/api/messaging/native_message_process_host.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <memory>
11 #include <utility>
12 #include <vector>
13
14 #include "base/base_paths.h"
15 #include "base/base_switches.h"
16 #include "base/bind.h"
17 #include "base/command_line.h"
18 #include "base/files/file.h"
19 #include "base/files/file_path.h"
20 #include "base/files/file_util.h"
21 #include "base/files/scoped_file.h"
22 #include "base/files/scoped_temp_dir.h"
23 #include "base/json/json_reader.h"
24 #include "base/path_service.h"
25 #include "base/process/process_metrics.h"
26 #include "base/rand_util.h"
27 #include "base/run_loop.h"
28 #include "base/strings/stringprintf.h"
29 #include "base/strings/utf_string_conversions.h"
30 #include "base/test/scoped_feature_list.h"
31 #include "base/test/test_timeouts.h"
32 #include "base/threading/platform_thread.h"
33 #include "base/time/time.h"
34 #include "build/build_config.h"
35 #include "chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.h"
36 #include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
37 #include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
38 #include "chrome/browser/profiles/profile.h"
39 #include "chrome/common/chrome_features.h"
40 #include "chrome/common/chrome_switches.h"
41 #include "chrome/test/base/testing_profile.h"
42 #include "components/version_info/version_info.h"
43 #include "content/public/browser/browser_task_traits.h"
44 #include "content/public/browser/browser_thread.h"
45 #include "content/public/test/browser_task_environment.h"
46 #include "extensions/common/extension.h"
47 #include "extensions/common/features/feature_channel.h"
48 #include "testing/gtest/include/gtest/gtest.h"
49
50 #if defined(OS_POSIX)
51 #include "base/files/file_descriptor_watcher_posix.h"
52 #endif
53
54 #if defined(OS_WIN)
55 #include <windows.h>
56 #include "base/win/scoped_handle.h"
57 #else
58 #include <unistd.h>
59 #endif
60
61 namespace {
62
63 const char kTestMessage[] = "{\"text\": \"Hello.\"}";
64
65 } // namespace
66
67 namespace extensions {
68
69 class FakeLauncher : public NativeProcessLauncher {
70 public:
FakeLauncher(base::File read_file,base::File write_file)71 FakeLauncher(base::File read_file, base::File write_file)
72 : read_file_(std::move(read_file)), write_file_(std::move(write_file)) {}
73
Create(base::FilePath read_file,base::FilePath write_file)74 static std::unique_ptr<NativeProcessLauncher> Create(
75 base::FilePath read_file,
76 base::FilePath write_file) {
77 int read_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
78 int write_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;
79 #if !defined(OS_POSIX)
80 read_flags |= base::File::FLAG_ASYNC;
81 write_flags |= base::File::FLAG_ASYNC;
82 #endif
83 return std::unique_ptr<NativeProcessLauncher>(
84 new FakeLauncher(base::File(read_file, read_flags),
85 base::File(write_file, write_flags)));
86 }
87
CreateWithPipeInput(base::File read_pipe,base::FilePath write_file)88 static std::unique_ptr<NativeProcessLauncher> CreateWithPipeInput(
89 base::File read_pipe,
90 base::FilePath write_file) {
91 int write_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE;
92 #if !defined(OS_POSIX)
93 write_flags |= base::File::FLAG_ASYNC;
94 #endif
95
96 return std::unique_ptr<NativeProcessLauncher>(new FakeLauncher(
97 std::move(read_pipe), base::File(write_file, write_flags)));
98 }
99
Launch(const GURL & origin,const std::string & native_host_name,const LaunchedCallback & callback) const100 void Launch(const GURL& origin,
101 const std::string& native_host_name,
102 const LaunchedCallback& callback) const override {
103 callback.Run(NativeProcessLauncher::RESULT_SUCCESS, base::Process(),
104 std::move(read_file_), std::move(write_file_));
105 }
106
107 private:
108 mutable base::File read_file_;
109 mutable base::File write_file_;
110 };
111
112 class NativeMessagingTest : public ::testing::Test,
113 public NativeMessageHost::Client,
114 public base::SupportsWeakPtr<NativeMessagingTest> {
115 protected:
NativeMessagingTest()116 NativeMessagingTest()
117 : current_channel_(version_info::Channel::DEV),
118 task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
119 channel_closed_(false) {}
120
SetUp()121 void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
122
TearDown()123 void TearDown() override {
124 if (native_message_host_) {
125 content::GetIOThreadTaskRunner({})->DeleteSoon(
126 FROM_HERE, native_message_host_.release());
127 }
128 base::RunLoop().RunUntilIdle();
129 }
130
PostMessageFromNativeHost(const std::string & message)131 void PostMessageFromNativeHost(const std::string& message) override {
132 last_message_ = message;
133
134 // Parse the message.
135 std::unique_ptr<base::DictionaryValue> dict_value =
136 base::DictionaryValue::From(base::JSONReader::ReadDeprecated(message));
137 if (dict_value) {
138 last_message_parsed_ = std::move(dict_value);
139 } else {
140 LOG(ERROR) << "Failed to parse " << message;
141 last_message_parsed_.reset();
142 }
143
144 if (run_loop_)
145 run_loop_->Quit();
146 }
147
CloseChannel(const std::string & error_message)148 void CloseChannel(const std::string& error_message) override {
149 channel_closed_ = true;
150 if (run_loop_)
151 run_loop_->Quit();
152 }
153
154 protected:
FormatMessage(const std::string & message)155 std::string FormatMessage(const std::string& message) {
156 uint32_t length = message.length();
157 return std::string(reinterpret_cast<char*>(&length), 4).append(message);
158 }
159
CreateTempFileWithMessage(const std::string & message)160 base::FilePath CreateTempFileWithMessage(const std::string& message) {
161 base::FilePath filename;
162 if (!base::CreateTemporaryFileInDir(temp_dir_.GetPath(), &filename))
163 return base::FilePath();
164
165 std::string message_with_header = FormatMessage(message);
166 if (!base::WriteFile(filename, message_with_header))
167 return base::FilePath();
168
169 return filename;
170 }
171
172 base::ScopedTempDir temp_dir_;
173 // Force the channel to be dev.
174 ScopedCurrentChannel current_channel_;
175 std::unique_ptr<NativeMessageHost> native_message_host_;
176 std::unique_ptr<base::RunLoop> run_loop_;
177 content::BrowserTaskEnvironment task_environment_;
178 TestingProfile profile_;
179
180 std::string last_message_;
181 std::unique_ptr<base::DictionaryValue> last_message_parsed_;
182 bool channel_closed_;
183 };
184
185 // Read a single message from a local file.
TEST_F(NativeMessagingTest,SingleSendMessageRead)186 TEST_F(NativeMessagingTest, SingleSendMessageRead) {
187 base::FilePath temp_output_file = temp_dir_.GetPath().AppendASCII("output");
188 #if defined(OS_WIN)
189 base::FilePath temp_input_file = CreateTempFileWithMessage(kTestMessage);
190 ASSERT_FALSE(temp_input_file.empty());
191 std::unique_ptr<NativeProcessLauncher> launcher =
192 FakeLauncher::Create(temp_input_file, temp_output_file);
193 #else // defined(OS_WIN)
194 base::PlatformFile pipe_handles[2];
195 ASSERT_EQ(0, pipe(pipe_handles));
196 base::File read_file(pipe_handles[0]);
197 std::string formatted_message = FormatMessage(kTestMessage);
198 ASSERT_GT(base::GetPageSize(), formatted_message.size());
199 ASSERT_TRUE(base::WriteFileDescriptor(
200 pipe_handles[1], formatted_message.data(), formatted_message.size()));
201 base::File write_file(pipe_handles[1]);
202 std::unique_ptr<NativeProcessLauncher> launcher =
203 FakeLauncher::CreateWithPipeInput(std::move(read_file), temp_output_file);
204 #endif // defined(OS_WIN)
205 native_message_host_ = NativeMessageProcessHost::CreateWithLauncher(
206 ScopedTestNativeMessagingHost::kExtensionId, "empty_app.py",
207 std::move(launcher));
208 ASSERT_TRUE(last_message_.empty());
209 native_message_host_->Start(this);
210
211 ASSERT_TRUE(native_message_host_);
212 run_loop_.reset(new base::RunLoop());
213 run_loop_->Run();
214
215 EXPECT_EQ(kTestMessage, last_message_);
216 }
217
218 // Tests sending a single message. The message should get written to
219 // |temp_file| and should match the contents of single_message_request.msg.
TEST_F(NativeMessagingTest,SingleSendMessageWrite)220 TEST_F(NativeMessagingTest, SingleSendMessageWrite) {
221 base::FilePath temp_output_file = temp_dir_.GetPath().AppendASCII("output");
222
223 base::File read_file;
224 #if defined(OS_WIN)
225 base::string16 pipe_name = base::StringPrintf(
226 L"\\\\.\\pipe\\chrome.nativeMessaging.out.%llx", base::RandUint64());
227 base::File write_handle =
228 base::File(base::ScopedPlatformFile(CreateNamedPipeW(
229 pipe_name.c_str(),
230 PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED |
231 FILE_FLAG_FIRST_PIPE_INSTANCE,
232 PIPE_TYPE_BYTE, 1, 0, 0, 5000, nullptr)),
233 true /* async */);
234 ASSERT_TRUE(write_handle.IsValid());
235 base::File read_handle =
236 base::File(base::ScopedPlatformFile(CreateFileW(
237 pipe_name.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING,
238 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr)),
239 true /* async */);
240 ASSERT_TRUE(read_handle.IsValid());
241
242 read_file = std::move(read_handle);
243 #else // defined(OS_WIN)
244 base::PlatformFile pipe_handles[2];
245 ASSERT_EQ(0, pipe(pipe_handles));
246 read_file = base::File(pipe_handles[0]);
247 base::File write_file(pipe_handles[1]);
248 #endif // !defined(OS_WIN)
249
250 std::unique_ptr<NativeProcessLauncher> launcher =
251 FakeLauncher::CreateWithPipeInput(std::move(read_file), temp_output_file);
252 native_message_host_ = NativeMessageProcessHost::CreateWithLauncher(
253 ScopedTestNativeMessagingHost::kExtensionId, "empty_app.py",
254 std::move(launcher));
255 native_message_host_->Start(this);
256 ASSERT_TRUE(native_message_host_);
257 base::RunLoop().RunUntilIdle();
258
259 native_message_host_->OnMessage(kTestMessage);
260 base::RunLoop().RunUntilIdle();
261
262 std::string output;
263 base::TimeTicks start_time = base::TimeTicks::Now();
264 while (base::TimeTicks::Now() - start_time < TestTimeouts::action_timeout()) {
265 ASSERT_TRUE(base::ReadFileToString(temp_output_file, &output));
266 if (!output.empty())
267 break;
268 base::PlatformThread::YieldCurrentThread();
269 }
270
271 EXPECT_EQ(FormatMessage(kTestMessage), output);
272 }
273
274 // Test send message with a real client. The client just echo's back the text
275 // it received.
TEST_F(NativeMessagingTest,EchoConnect)276 TEST_F(NativeMessagingTest, EchoConnect) {
277 ScopedTestNativeMessagingHost test_host;
278 ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
279 std::string error_message;
280 native_message_host_ = NativeMessageProcessHost::Create(
281 &profile_, NULL, ScopedTestNativeMessagingHost::kExtensionId,
282 ScopedTestNativeMessagingHost::kHostName, false, &error_message);
283 native_message_host_->Start(this);
284 ASSERT_TRUE(native_message_host_);
285
286 native_message_host_->OnMessage("{\"text\": \"Hello.\"}");
287 run_loop_.reset(new base::RunLoop());
288 run_loop_->Run();
289 ASSERT_FALSE(last_message_.empty());
290 ASSERT_TRUE(last_message_parsed_);
291
292 std::string expected_url = std::string("chrome-extension://") +
293 ScopedTestNativeMessagingHost::kExtensionId + "/";
294 int id;
295 EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
296 EXPECT_EQ(1, id);
297 std::string text;
298 EXPECT_TRUE(last_message_parsed_->GetString("echo.text", &text));
299 EXPECT_EQ("Hello.", text);
300 std::string url;
301 EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url));
302 EXPECT_EQ(expected_url, url);
303
304 native_message_host_->OnMessage("{\"foo\": \"bar\"}");
305 run_loop_.reset(new base::RunLoop());
306 run_loop_->Run();
307 EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
308 EXPECT_EQ(2, id);
309 EXPECT_TRUE(last_message_parsed_->GetString("echo.foo", &text));
310 EXPECT_EQ("bar", text);
311 EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url));
312 EXPECT_EQ(expected_url, url);
313
314 const base::Value* args = nullptr;
315 ASSERT_TRUE(last_message_parsed_->Get("args", &args));
316 EXPECT_TRUE(args->is_none());
317
318 const base::Value* connect_id_value = nullptr;
319 ASSERT_TRUE(last_message_parsed_->Get("connect_id", &connect_id_value));
320 EXPECT_TRUE(connect_id_value->is_none());
321 }
322
323 // Test send message with a real client. The args passed when launching the
324 // native messaging host should contain reconnect args.
325 //
326 // TODO(crbug.com/1026121): Fix it. This test is flaky on Win7 bots.
327 #if defined(OS_WIN)
328 #define MAYBE_ReconnectArgs DISABLED_ReconnectArgs
329 #else
330 #define MAYBE_ReconnectArgs ReconnectArgs
331 #endif
TEST_F(NativeMessagingTest,MAYBE_ReconnectArgs)332 TEST_F(NativeMessagingTest, MAYBE_ReconnectArgs) {
333 base::test::ScopedFeatureList feature_list;
334 feature_list.InitAndEnableFeature(features::kOnConnectNative);
335 ScopedAllowNativeAppConnectionForTest allow_native_app_connection(true);
336 ScopedTestNativeMessagingHost test_host;
337 ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
338 std::string error_message;
339 native_message_host_ = NativeMessageProcessHost::Create(
340 &profile_, NULL, ScopedTestNativeMessagingHost::kExtensionId,
341 ScopedTestNativeMessagingHost::
342 kSupportsNativeInitiatedConnectionsHostName,
343 false, &error_message);
344 native_message_host_->Start(this);
345 ASSERT_TRUE(native_message_host_);
346
347 native_message_host_->OnMessage("{\"text\": \"Hello.\"}");
348 run_loop_.reset(new base::RunLoop());
349 run_loop_->Run();
350 ASSERT_FALSE(last_message_.empty());
351 ASSERT_TRUE(last_message_parsed_);
352
353 const base::ListValue* args_value = nullptr;
354 ASSERT_TRUE(last_message_parsed_->GetList("args", &args_value));
355 std::vector<base::CommandLine::StringType> args;
356 args.reserve(args_value->GetSize());
357 for (auto& arg : args_value->GetList()) {
358 ASSERT_TRUE(arg.is_string());
359 #if defined(OS_WIN)
360 args.push_back(base::UTF8ToUTF16(arg.GetString()));
361 #else
362 args.push_back(arg.GetString());
363 #endif
364 }
365 base::CommandLine cmd_line(args);
366 base::FilePath exe_path;
367 ASSERT_TRUE(base::PathService::Get(base::FILE_EXE, &exe_path));
368 EXPECT_EQ(exe_path, cmd_line.GetProgram());
369 EXPECT_TRUE(cmd_line.HasSwitch(switches::kNoStartupWindow));
370 EXPECT_EQ(
371 ScopedTestNativeMessagingHost::
372 kSupportsNativeInitiatedConnectionsHostName,
373 cmd_line.GetSwitchValueASCII(switches::kNativeMessagingConnectHost));
374 EXPECT_EQ(
375 ScopedTestNativeMessagingHost::kExtensionId,
376 cmd_line.GetSwitchValueASCII(switches::kNativeMessagingConnectExtension));
377 EXPECT_EQ(features::kOnConnectNative.name,
378 cmd_line.GetSwitchValueASCII(switches::kEnableFeatures));
379 EXPECT_EQ(profile_.GetPath().BaseName(),
380 cmd_line.GetSwitchValuePath(switches::kProfileDirectory));
381 EXPECT_EQ(profile_.GetPath().DirName(),
382 cmd_line.GetSwitchValuePath(switches::kUserDataDir));
383 }
384
385 // Test send message with a real client. The args passed when launching the
386 // native messaging host should not contain reconnect args.
TEST_F(NativeMessagingTest,ReconnectArgs_Disabled)387 TEST_F(NativeMessagingTest, ReconnectArgs_Disabled) {
388 base::test::ScopedFeatureList feature_list;
389 feature_list.InitAndDisableFeature(features::kOnConnectNative);
390 ScopedTestNativeMessagingHost test_host;
391 ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
392 std::string error_message;
393 native_message_host_ = NativeMessageProcessHost::Create(
394 &profile_, NULL, ScopedTestNativeMessagingHost::kExtensionId,
395 ScopedTestNativeMessagingHost::
396 kSupportsNativeInitiatedConnectionsHostName,
397 false, &error_message);
398 native_message_host_->Start(this);
399 ASSERT_TRUE(native_message_host_);
400
401 native_message_host_->OnMessage("{\"text\": \"Hello.\"}");
402 run_loop_.reset(new base::RunLoop());
403 run_loop_->Run();
404 ASSERT_FALSE(last_message_.empty());
405 ASSERT_TRUE(last_message_parsed_);
406
407 const base::Value* args = nullptr;
408 ASSERT_TRUE(last_message_parsed_->Get("args", &args));
409 EXPECT_TRUE(args->is_none());
410 }
411
412 // Test that reconnect args are not sent if the extension is not permitted to
413 // receive natively-established connections.
TEST_F(NativeMessagingTest,ReconnectArgsIfNativeConnectionDisallowed)414 TEST_F(NativeMessagingTest, ReconnectArgsIfNativeConnectionDisallowed) {
415 base::test::ScopedFeatureList feature_list;
416 feature_list.InitAndEnableFeature(features::kOnConnectNative);
417 ScopedAllowNativeAppConnectionForTest disallow_native_app_connection(false);
418 ScopedTestNativeMessagingHost test_host;
419 ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
420 std::string error_message;
421 native_message_host_ = NativeMessageProcessHost::Create(
422 &profile_, NULL, ScopedTestNativeMessagingHost::kExtensionId,
423 ScopedTestNativeMessagingHost::
424 kSupportsNativeInitiatedConnectionsHostName,
425 false, &error_message);
426 native_message_host_->Start(this);
427 ASSERT_TRUE(native_message_host_);
428
429 native_message_host_->OnMessage("{\"text\": \"Hello.\"}");
430 run_loop_.reset(new base::RunLoop());
431 run_loop_->Run();
432 ASSERT_FALSE(last_message_.empty());
433 ASSERT_TRUE(last_message_parsed_);
434
435 const base::Value* args_value = nullptr;
436 ASSERT_TRUE(last_message_parsed_->Get("args", &args_value));
437 EXPECT_TRUE(args_value->is_none());
438
439 const base::Value* connect_id_value = nullptr;
440 ASSERT_TRUE(last_message_parsed_->Get("connect_id", &connect_id_value));
441 EXPECT_TRUE(connect_id_value->is_none());
442 }
443
TEST_F(NativeMessagingTest,UserLevel)444 TEST_F(NativeMessagingTest, UserLevel) {
445 ScopedTestNativeMessagingHost test_host;
446 ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
447
448 std::string error_message;
449 native_message_host_ = NativeMessageProcessHost::Create(
450 &profile_, NULL, ScopedTestNativeMessagingHost::kExtensionId,
451 ScopedTestNativeMessagingHost::kHostName, true, &error_message);
452 native_message_host_->Start(this);
453 ASSERT_TRUE(native_message_host_);
454
455 native_message_host_->OnMessage("{\"text\": \"Hello.\"}");
456 run_loop_.reset(new base::RunLoop());
457 run_loop_->Run();
458 ASSERT_FALSE(last_message_.empty());
459 ASSERT_TRUE(last_message_parsed_);
460 }
461
TEST_F(NativeMessagingTest,DisallowUserLevel)462 TEST_F(NativeMessagingTest, DisallowUserLevel) {
463 ScopedTestNativeMessagingHost test_host;
464 ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
465
466 std::string error_message;
467 native_message_host_ = NativeMessageProcessHost::Create(
468 &profile_, NULL, ScopedTestNativeMessagingHost::kExtensionId,
469 ScopedTestNativeMessagingHost::kHostName, false, &error_message);
470 native_message_host_->Start(this);
471 ASSERT_TRUE(native_message_host_);
472 run_loop_.reset(new base::RunLoop());
473 run_loop_->Run();
474
475 // The host should fail to start.
476 ASSERT_TRUE(channel_closed_);
477 }
478
479 } // namespace extensions
480