Home | History | Annotate | Download | only in messaging
      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 "base/bind.h"
      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/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"
     31 
     32 #if defined(OS_WIN)
     33 #include <windows.h>
     34 #include "base/win/scoped_handle.h"
     35 #else
     36 #include <unistd.h>
     37 #endif
     38 
     39 using content::BrowserThread;
     40 
     41 namespace {
     42 
     43 const char kTestMessage[] = "{\"text\": \"Hello.\"}";
     44 
     45 }  // namespace
     46 
     47 namespace extensions {
     48 
     49 class FakeLauncher : public NativeProcessLauncher {
     50  public:
     51   FakeLauncher(base::File read_file, base::File write_file)
     52       : read_file_(read_file.Pass()),
     53         write_file_(write_file.Pass()) {
     54   }
     55 
     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;
     63 #endif
     64     return scoped_ptr<NativeProcessLauncher>(new FakeLauncher(
     65         base::File(read_file, read_flags),
     66         base::File(write_file, write_flags)));
     67   }
     68 
     69   static scoped_ptr<NativeProcessLauncher> CreateWithPipeInput(
     70       base::File read_pipe,
     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;
     75 #endif
     76 
     77     return scoped_ptr<NativeProcessLauncher>(new FakeLauncher(
     78         read_pipe.Pass(),
     79         base::File(write_file, write_flags)));
     80   }
     81 
     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());
     88   }
     89 
     90  private:
     91   mutable base::File read_file_;
     92   mutable base::File write_file_;
     93 };
     94 
     95 class NativeMessagingTest : public ::testing::Test,
     96                             public NativeMessageProcessHost::Client,
     97                             public base::SupportsWeakPtr<NativeMessagingTest> {
     98  protected:
     99   NativeMessagingTest()
    100       : current_channel_(chrome::VersionInfo::CHANNEL_DEV),
    101         thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
    102         channel_closed_(false) {}
    103 
    104   virtual void SetUp() OVERRIDE {
    105     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    106   }
    107 
    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());
    112     }
    113     base::RunLoop().RunUntilIdle();
    114   }
    115 
    116   virtual void PostMessageFromNativeProcess(
    117       int port_id,
    118       const std::string& message) OVERRIDE  {
    119     last_message_ = message;
    120 
    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);
    126     } else {
    127       LOG(ERROR) << "Failed to parse " << message;
    128       last_message_parsed_.reset();
    129       delete parsed;
    130     }
    131 
    132     if (run_loop_)
    133       run_loop_->Quit();
    134   }
    135 
    136   virtual void CloseChannel(int port_id,
    137                             const std::string& error_message) OVERRIDE {
    138     channel_closed_ = true;
    139     if (run_loop_)
    140       run_loop_->Quit();
    141   }
    142 
    143  protected:
    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);
    147   }
    148 
    149   base::FilePath CreateTempFileWithMessage(const std::string& message) {
    150     base::FilePath filename;
    151     if (!base::CreateTemporaryFileInDir(temp_dir_.path(), &filename))
    152       return base::FilePath();
    153 
    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();
    160     }
    161     return filename;
    162   }
    163 
    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_;
    173 };
    174 
    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());
    180 
    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",
    185       0, launcher.Pass());
    186   ASSERT_TRUE(native_message_process_host_.get());
    187   run_loop_.reset(new base::RunLoop());
    188   run_loop_->RunUntilIdle();
    189 
    190   if (last_message_.empty()) {
    191     run_loop_.reset(new base::RunLoop());
    192     native_message_process_host_->ReadNowForTesting();
    193     run_loop_->Run();
    194   }
    195   EXPECT_EQ(kTestMessage, last_message_);
    196 }
    197 
    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");
    202 
    203   base::File read_file;
    204 #if defined(OS_WIN)
    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());
    217 
    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)
    225 
    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",
    231       0, launcher.Pass());
    232   ASSERT_TRUE(native_message_process_host_.get());
    233   base::RunLoop().RunUntilIdle();
    234 
    235   native_message_process_host_->Send(kTestMessage);
    236   base::RunLoop().RunUntilIdle();
    237 
    238   std::string output;
    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));
    242     if (!output.empty())
    243       break;
    244     base::PlatformThread::YieldCurrentThread();
    245   }
    246 
    247   EXPECT_EQ(FormatMessage(kTestMessage), output);
    248 }
    249 
    250 // Test send message with a real client. The client just echo's back the text
    251 // it received.
    252 TEST_F(NativeMessagingTest, EchoConnect) {
    253   ScopedTestNativeMessagingHost test_host;
    254   ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
    255 
    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());
    260 
    261   native_message_process_host_->Send("{\"text\": \"Hello.\"}");
    262   run_loop_.reset(new base::RunLoop());
    263   run_loop_->Run();
    264   ASSERT_FALSE(last_message_.empty());
    265   ASSERT_TRUE(last_message_parsed_);
    266 
    267   std::string expected_url = std::string("chrome-extension://") +
    268       ScopedTestNativeMessagingHost::kExtensionId + "/";
    269   int id;
    270   EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
    271   EXPECT_EQ(1, id);
    272   std::string text;
    273   EXPECT_TRUE(last_message_parsed_->GetString("echo.text", &text));
    274   EXPECT_EQ("Hello.", text);
    275   std::string url;
    276   EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url));
    277   EXPECT_EQ(expected_url, url);
    278 
    279   native_message_process_host_->Send("{\"foo\": \"bar\"}");
    280   run_loop_.reset(new base::RunLoop());
    281   run_loop_->Run();
    282   EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id));
    283   EXPECT_EQ(2, 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);
    288 }
    289 
    290 TEST_F(NativeMessagingTest, UserLevel) {
    291   ScopedTestNativeMessagingHost test_host;
    292   ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
    293 
    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());
    298 
    299   native_message_process_host_->Send("{\"text\": \"Hello.\"}");
    300   run_loop_.reset(new base::RunLoop());
    301   run_loop_->Run();
    302   ASSERT_FALSE(last_message_.empty());
    303   ASSERT_TRUE(last_message_parsed_);
    304 }
    305 
    306 TEST_F(NativeMessagingTest, DisallowUserLevel) {
    307   ScopedTestNativeMessagingHost test_host;
    308   ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
    309 
    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());
    315   run_loop_->Run();
    316 
    317   // The host should fail to start.
    318   ASSERT_TRUE(channel_closed_);
    319 }
    320 
    321 }  // namespace extensions
    322