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/command_line.h" 7 #include "base/file_util.h" 8 #include "base/files/file_path.h" 9 #include "base/files/scoped_temp_dir.h" 10 #include "base/json/json_reader.h" 11 #include "base/memory/scoped_ptr.h" 12 #include "base/memory/weak_ptr.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/path_service.h" 15 #include "base/platform_file.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_paths.h" 26 #include "chrome/common/chrome_switches.h" 27 #include "chrome/common/chrome_version_info.h" 28 #include "chrome/common/extensions/extension.h" 29 #include "chrome/common/extensions/features/feature_channel.h" 30 #include "content/public/browser/browser_thread.h" 31 #include "content/public/test/test_browser_thread_bundle.h" 32 #include "testing/gtest/include/gtest/gtest.h" 33 34 using content::BrowserThread; 35 36 namespace { 37 38 const char kTestMessage[] = "{\"text\": \"Hello.\"}"; 39 40 base::FilePath GetTestDir() { 41 base::FilePath test_dir; 42 PathService::Get(chrome::DIR_TEST_DATA, &test_dir); 43 test_dir = test_dir.AppendASCII("native_messaging"); 44 return test_dir; 45 } 46 47 } // namespace 48 49 namespace extensions { 50 51 class FakeLauncher : public NativeProcessLauncher { 52 public: 53 FakeLauncher(base::FilePath read_file, base::FilePath write_file) { 54 read_file_ = base::CreatePlatformFile( 55 read_file, 56 base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ | 57 base::PLATFORM_FILE_ASYNC, 58 NULL, NULL); 59 write_file_ = base::CreatePlatformFile( 60 write_file, 61 base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE | 62 base::PLATFORM_FILE_ASYNC, 63 NULL, NULL); 64 } 65 66 virtual void Launch(const GURL& origin, 67 const std::string& native_host_name, 68 LaunchedCallback callback) const OVERRIDE { 69 callback.Run(NativeProcessLauncher::RESULT_SUCCESS, 70 read_file_, write_file_); 71 } 72 73 private: 74 base::PlatformFile read_file_; 75 base::PlatformFile write_file_; 76 }; 77 78 class NativeMessagingTest : public ::testing::Test, 79 public NativeMessageProcessHost::Client, 80 public base::SupportsWeakPtr<NativeMessagingTest> { 81 protected: 82 NativeMessagingTest() 83 : current_channel_(chrome::VersionInfo::CHANNEL_DEV), 84 thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {} 85 86 virtual void SetUp() OVERRIDE { 87 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 88 // Change the user data dir so native apps will be looked for in the test 89 // directory. 90 ASSERT_TRUE(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir_)); 91 ASSERT_TRUE(PathService::Override(chrome::DIR_USER_DATA, GetTestDir())); 92 } 93 94 virtual void TearDown() OVERRIDE { 95 // Change the user data dir back for other tests. 96 ASSERT_TRUE(PathService::Override(chrome::DIR_USER_DATA, user_data_dir_)); 97 if (native_message_process_host_.get()) { 98 BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, 99 native_message_process_host_.release()); 100 } 101 base::RunLoop().RunUntilIdle(); 102 } 103 104 virtual void PostMessageFromNativeProcess( 105 int port_id, 106 const std::string& message) OVERRIDE { 107 last_message_ = message; 108 109 // Parse the message. 110 base::Value* parsed = base::JSONReader::Read(message); 111 base::DictionaryValue* dict_value; 112 if (parsed && parsed->GetAsDictionary(&dict_value)) { 113 last_message_parsed_.reset(dict_value); 114 } else { 115 LOG(ERROR) << "Failed to parse " << message; 116 last_message_parsed_.reset(); 117 delete parsed; 118 } 119 120 if (read_message_run_loop_) 121 read_message_run_loop_->Quit(); 122 } 123 124 virtual void CloseChannel(int port_id, 125 const std::string& error_message) OVERRIDE { 126 } 127 128 protected: 129 std::string FormatMessage(const std::string& message) { 130 Pickle pickle; 131 pickle.WriteString(message); 132 return std::string(const_cast<const Pickle*>(&pickle)->payload(), 133 pickle.payload_size()); 134 } 135 136 base::FilePath CreateTempFileWithMessage(const std::string& message) { 137 base::FilePath filename = temp_dir_.path().AppendASCII("input"); 138 file_util::CreateTemporaryFile(&filename); 139 std::string message_with_header = FormatMessage(message); 140 EXPECT_TRUE(file_util::WriteFile( 141 filename, message_with_header.data(), message_with_header.size())); 142 return filename; 143 } 144 145 // Force the channel to be dev. 146 base::ScopedTempDir temp_dir_; 147 ScopedCurrentChannel current_channel_; 148 scoped_ptr<NativeMessageProcessHost> native_message_process_host_; 149 base::FilePath user_data_dir_; 150 scoped_ptr<base::RunLoop> read_message_run_loop_; 151 content::TestBrowserThreadBundle thread_bundle_; 152 std::string last_message_; 153 scoped_ptr<base::DictionaryValue> last_message_parsed_; 154 }; 155 156 // Read a single message from a local file. 157 TEST_F(NativeMessagingTest, SingleSendMessageRead) { 158 base::FilePath temp_output_file = temp_dir_.path().AppendASCII("output"); 159 base::FilePath temp_input_file = CreateTempFileWithMessage(kTestMessage); 160 161 scoped_ptr<NativeProcessLauncher> launcher( 162 new FakeLauncher(temp_input_file, temp_output_file)); 163 native_message_process_host_ = NativeMessageProcessHost::CreateWithLauncher( 164 AsWeakPtr(), kTestNativeMessagingExtensionId, "empty_app.py", 165 0, launcher.Pass()); 166 ASSERT_TRUE(native_message_process_host_.get()); 167 read_message_run_loop_.reset(new base::RunLoop()); 168 read_message_run_loop_->RunUntilIdle(); 169 170 if (last_message_.empty()) { 171 read_message_run_loop_.reset(new base::RunLoop()); 172 native_message_process_host_->ReadNowForTesting(); 173 read_message_run_loop_->Run(); 174 } 175 EXPECT_EQ(kTestMessage, last_message_); 176 } 177 178 // Tests sending a single message. The message should get written to 179 // |temp_file| and should match the contents of single_message_request.msg. 180 TEST_F(NativeMessagingTest, SingleSendMessageWrite) { 181 base::FilePath temp_output_file = temp_dir_.path().AppendASCII("output"); 182 base::FilePath temp_input_file = CreateTempFileWithMessage(std::string()); 183 184 scoped_ptr<NativeProcessLauncher> launcher( 185 new FakeLauncher(temp_input_file, temp_output_file)); 186 native_message_process_host_ = NativeMessageProcessHost::CreateWithLauncher( 187 AsWeakPtr(), kTestNativeMessagingExtensionId, "empty_app.py", 188 0, launcher.Pass()); 189 ASSERT_TRUE(native_message_process_host_.get()); 190 base::RunLoop().RunUntilIdle(); 191 192 native_message_process_host_->Send(kTestMessage); 193 base::RunLoop().RunUntilIdle(); 194 195 std::string output; 196 base::TimeTicks start_time = base::TimeTicks::Now(); 197 while (base::TimeTicks::Now() - start_time < TestTimeouts::action_timeout()) { 198 ASSERT_TRUE(file_util::ReadFileToString(temp_output_file, &output)); 199 if (!output.empty()) 200 break; 201 base::PlatformThread::YieldCurrentThread(); 202 } 203 204 EXPECT_EQ(FormatMessage(kTestMessage), output); 205 } 206 207 // Test send message with a real client. The client just echo's back the text 208 // it received. 209 TEST_F(NativeMessagingTest, EchoConnect) { 210 base::FilePath manifest_path = temp_dir_.path().AppendASCII( 211 std::string(kTestNativeMessagingHostName) + ".json"); 212 ASSERT_NO_FATAL_FAILURE(CreateTestNativeHostManifest(manifest_path)); 213 214 std::string hosts_option = base::StringPrintf( 215 "%s=%s", extensions::kTestNativeMessagingHostName, 216 manifest_path.AsUTF8Unsafe().c_str()); 217 CommandLine::ForCurrentProcess()->AppendSwitchASCII( 218 switches::kNativeMessagingHosts, hosts_option); 219 220 native_message_process_host_ = NativeMessageProcessHost::Create( 221 gfx::NativeView(), AsWeakPtr(), kTestNativeMessagingExtensionId, 222 kTestNativeMessagingHostName, 0); 223 ASSERT_TRUE(native_message_process_host_.get()); 224 225 native_message_process_host_->Send("{\"text\": \"Hello.\"}"); 226 read_message_run_loop_.reset(new base::RunLoop()); 227 read_message_run_loop_->Run(); 228 ASSERT_FALSE(last_message_.empty()); 229 ASSERT_TRUE(last_message_parsed_); 230 231 std::string expected_url = std::string("chrome-extension://") + 232 kTestNativeMessagingExtensionId + "/"; 233 int id; 234 EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id)); 235 EXPECT_EQ(1, id); 236 std::string text; 237 EXPECT_TRUE(last_message_parsed_->GetString("echo.text", &text)); 238 EXPECT_EQ("Hello.", text); 239 std::string url; 240 EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url)); 241 EXPECT_EQ(expected_url, url); 242 243 244 native_message_process_host_->Send("{\"foo\": \"bar\"}"); 245 read_message_run_loop_.reset(new base::RunLoop()); 246 read_message_run_loop_->Run(); 247 EXPECT_TRUE(last_message_parsed_->GetInteger("id", &id)); 248 EXPECT_EQ(2, id); 249 EXPECT_TRUE(last_message_parsed_->GetString("echo.foo", &text)); 250 EXPECT_EQ("bar", text); 251 EXPECT_TRUE(last_message_parsed_->GetString("caller_url", &url)); 252 EXPECT_EQ(expected_url, url); 253 } 254 255 } // namespace extensions 256