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