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