1 // Copyright 2013 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 "storage/browser/fileapi/file_system_dir_url_request_job.h" 6 7 #include <string> 8 9 #include "base/files/file_path.h" 10 #include "base/files/file_util.h" 11 #include "base/files/scoped_temp_dir.h" 12 #include "base/format_macros.h" 13 #include "base/memory/scoped_vector.h" 14 #include "base/memory/weak_ptr.h" 15 #include "base/message_loop/message_loop.h" 16 #include "base/run_loop.h" 17 #include "base/strings/string_piece.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "content/public/test/mock_special_storage_policy.h" 20 #include "content/public/test/test_file_system_backend.h" 21 #include "content/public/test/test_file_system_context.h" 22 #include "net/base/net_errors.h" 23 #include "net/base/net_util.h" 24 #include "net/base/request_priority.h" 25 #include "net/http/http_request_headers.h" 26 #include "net/url_request/url_request.h" 27 #include "net/url_request/url_request_context.h" 28 #include "net/url_request/url_request_test_util.h" 29 #include "storage/browser/fileapi/external_mount_points.h" 30 #include "storage/browser/fileapi/file_system_context.h" 31 #include "storage/browser/fileapi/file_system_file_util.h" 32 #include "storage/browser/fileapi/file_system_operation_context.h" 33 #include "storage/browser/fileapi/file_system_url.h" 34 #include "testing/gtest/include/gtest/gtest.h" 35 #include "third_party/icu/source/i18n/unicode/regex.h" 36 37 using storage::FileSystemContext; 38 using storage::FileSystemOperationContext; 39 using storage::FileSystemURL; 40 41 namespace content { 42 namespace { 43 44 // We always use the TEMPORARY FileSystem in this test. 45 const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/"; 46 47 const char kValidExternalMountPoint[] = "mnt_name"; 48 49 // An auto mounter that will try to mount anything for |storage_domain| = 50 // "automount", but will only succeed for the mount point "mnt_name". 51 bool TestAutoMountForURLRequest( 52 const net::URLRequest* /*url_request*/, 53 const storage::FileSystemURL& filesystem_url, 54 const std::string& storage_domain, 55 const base::Callback<void(base::File::Error result)>& callback) { 56 if (storage_domain != "automount") 57 return false; 58 59 std::vector<base::FilePath::StringType> components; 60 filesystem_url.path().GetComponents(&components); 61 std::string mount_point = base::FilePath(components[0]).AsUTF8Unsafe(); 62 63 if (mount_point == kValidExternalMountPoint) { 64 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( 65 kValidExternalMountPoint, 66 storage::kFileSystemTypeTest, 67 storage::FileSystemMountOption(), 68 base::FilePath()); 69 callback.Run(base::File::FILE_OK); 70 } else { 71 callback.Run(base::File::FILE_ERROR_NOT_FOUND); 72 } 73 return true; 74 } 75 76 class FileSystemDirURLRequestJobFactory : public net::URLRequestJobFactory { 77 public: 78 FileSystemDirURLRequestJobFactory(const std::string& storage_domain, 79 FileSystemContext* context) 80 : storage_domain_(storage_domain), file_system_context_(context) { 81 } 82 83 virtual net::URLRequestJob* MaybeCreateJobWithProtocolHandler( 84 const std::string& scheme, 85 net::URLRequest* request, 86 net::NetworkDelegate* network_delegate) const OVERRIDE { 87 return new storage::FileSystemDirURLRequestJob( 88 request, network_delegate, storage_domain_, file_system_context_); 89 } 90 91 virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE { 92 return true; 93 } 94 95 virtual bool IsHandledURL(const GURL& url) const OVERRIDE { 96 return true; 97 } 98 99 virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE { 100 return false; 101 } 102 103 private: 104 std::string storage_domain_; 105 FileSystemContext* file_system_context_; 106 }; 107 108 109 } // namespace 110 111 class FileSystemDirURLRequestJobTest : public testing::Test { 112 protected: 113 FileSystemDirURLRequestJobTest() 114 : weak_factory_(this) { 115 } 116 117 virtual void SetUp() OVERRIDE { 118 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 119 120 special_storage_policy_ = new MockSpecialStoragePolicy; 121 file_system_context_ = CreateFileSystemContextForTesting( 122 NULL, temp_dir_.path()); 123 124 file_system_context_->OpenFileSystem( 125 GURL("http://remote/"), 126 storage::kFileSystemTypeTemporary, 127 storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, 128 base::Bind(&FileSystemDirURLRequestJobTest::OnOpenFileSystem, 129 weak_factory_.GetWeakPtr())); 130 base::RunLoop().RunUntilIdle(); 131 } 132 133 virtual void TearDown() OVERRIDE { 134 // NOTE: order matters, request must die before delegate 135 request_.reset(NULL); 136 delegate_.reset(NULL); 137 } 138 139 void SetUpAutoMountContext(base::FilePath* mnt_point) { 140 *mnt_point = temp_dir_.path().AppendASCII("auto_mount_dir"); 141 ASSERT_TRUE(base::CreateDirectory(*mnt_point)); 142 143 ScopedVector<storage::FileSystemBackend> additional_providers; 144 additional_providers.push_back(new TestFileSystemBackend( 145 base::MessageLoopProxy::current().get(), *mnt_point)); 146 147 std::vector<storage::URLRequestAutoMountHandler> handlers; 148 handlers.push_back(base::Bind(&TestAutoMountForURLRequest)); 149 150 file_system_context_ = CreateFileSystemContextWithAutoMountersForTesting( 151 NULL, additional_providers.Pass(), handlers, temp_dir_.path()); 152 } 153 154 void OnOpenFileSystem(const GURL& root_url, 155 const std::string& name, 156 base::File::Error result) { 157 ASSERT_EQ(base::File::FILE_OK, result); 158 } 159 160 void TestRequestHelper(const GURL& url, bool run_to_completion, 161 FileSystemContext* file_system_context) { 162 delegate_.reset(new net::TestDelegate()); 163 delegate_->set_quit_on_redirect(true); 164 job_factory_.reset(new FileSystemDirURLRequestJobFactory( 165 url.GetOrigin().host(), file_system_context)); 166 empty_context_.set_job_factory(job_factory_.get()); 167 168 request_ = empty_context_.CreateRequest( 169 url, net::DEFAULT_PRIORITY, delegate_.get(), NULL); 170 request_->Start(); 171 ASSERT_TRUE(request_->is_pending()); // verify that we're starting async 172 if (run_to_completion) 173 base::MessageLoop::current()->Run(); 174 } 175 176 void TestRequest(const GURL& url) { 177 TestRequestHelper(url, true, file_system_context_.get()); 178 } 179 180 void TestRequestWithContext(const GURL& url, 181 FileSystemContext* file_system_context) { 182 TestRequestHelper(url, true, file_system_context); 183 } 184 185 void TestRequestNoRun(const GURL& url) { 186 TestRequestHelper(url, false, file_system_context_.get()); 187 } 188 189 FileSystemURL CreateURL(const base::FilePath& file_path) { 190 return file_system_context_->CreateCrackedFileSystemURL( 191 GURL("http://remote"), storage::kFileSystemTypeTemporary, file_path); 192 } 193 194 FileSystemOperationContext* NewOperationContext() { 195 FileSystemOperationContext* context( 196 new FileSystemOperationContext(file_system_context_.get())); 197 context->set_allowed_bytes_growth(1024); 198 return context; 199 } 200 201 void CreateDirectory(const base::StringPiece& dir_name) { 202 base::FilePath path = base::FilePath().AppendASCII(dir_name); 203 scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); 204 ASSERT_EQ(base::File::FILE_OK, file_util()->CreateDirectory( 205 context.get(), 206 CreateURL(path), 207 false /* exclusive */, 208 false /* recursive */)); 209 } 210 211 void EnsureFileExists(const base::StringPiece file_name) { 212 base::FilePath path = base::FilePath().AppendASCII(file_name); 213 scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); 214 ASSERT_EQ(base::File::FILE_OK, file_util()->EnsureFileExists( 215 context.get(), CreateURL(path), NULL)); 216 } 217 218 void TruncateFile(const base::StringPiece file_name, int64 length) { 219 base::FilePath path = base::FilePath().AppendASCII(file_name); 220 scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); 221 ASSERT_EQ(base::File::FILE_OK, file_util()->Truncate( 222 context.get(), CreateURL(path), length)); 223 } 224 225 base::File::Error GetFileInfo(const base::FilePath& path, 226 base::File::Info* file_info, 227 base::FilePath* platform_file_path) { 228 scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); 229 return file_util()->GetFileInfo(context.get(), 230 CreateURL(path), 231 file_info, platform_file_path); 232 } 233 234 // If |size| is negative, the reported size is ignored. 235 void VerifyListingEntry(const std::string& entry_line, 236 const std::string& name, 237 const std::string& url, 238 bool is_directory, 239 int64 size) { 240 #define STR "([^\"]*)" 241 icu::UnicodeString pattern("^<script>addRow\\(\"" STR "\",\"" STR 242 "\",(0|1),\"" STR "\",\"" STR "\"\\);</script>"); 243 #undef STR 244 icu::UnicodeString input(entry_line.c_str()); 245 246 UErrorCode status = U_ZERO_ERROR; 247 icu::RegexMatcher match(pattern, input, 0, status); 248 249 EXPECT_TRUE(match.find()); 250 EXPECT_EQ(5, match.groupCount()); 251 EXPECT_EQ(icu::UnicodeString(name.c_str()), match.group(1, status)); 252 EXPECT_EQ(icu::UnicodeString(url.c_str()), match.group(2, status)); 253 EXPECT_EQ(icu::UnicodeString(is_directory ? "1" : "0"), 254 match.group(3, status)); 255 if (size >= 0) { 256 icu::UnicodeString size_string(FormatBytesUnlocalized(size).c_str()); 257 EXPECT_EQ(size_string, match.group(4, status)); 258 } 259 260 base::Time date; 261 icu::UnicodeString date_ustr(match.group(5, status)); 262 std::string date_str; 263 base::UTF16ToUTF8(date_ustr.getBuffer(), date_ustr.length(), &date_str); 264 EXPECT_TRUE(base::Time::FromString(date_str.c_str(), &date)); 265 EXPECT_FALSE(date.is_null()); 266 } 267 268 GURL CreateFileSystemURL(const std::string path) { 269 return GURL(kFileSystemURLPrefix + path); 270 } 271 272 storage::FileSystemFileUtil* file_util() { 273 return file_system_context_->sandbox_delegate()->sync_file_util(); 274 } 275 276 // Put the message loop at the top, so that it's the last thing deleted. 277 // Delete all MessageLoopProxy objects before the MessageLoop, to help prevent 278 // leaks caused by tasks posted during shutdown. 279 base::MessageLoopForIO message_loop_; 280 281 base::ScopedTempDir temp_dir_; 282 net::URLRequestContext empty_context_; 283 scoped_ptr<net::TestDelegate> delegate_; 284 scoped_ptr<net::URLRequest> request_; 285 scoped_ptr<FileSystemDirURLRequestJobFactory> job_factory_; 286 scoped_refptr<MockSpecialStoragePolicy> special_storage_policy_; 287 scoped_refptr<FileSystemContext> file_system_context_; 288 base::WeakPtrFactory<FileSystemDirURLRequestJobTest> weak_factory_; 289 }; 290 291 namespace { 292 293 TEST_F(FileSystemDirURLRequestJobTest, DirectoryListing) { 294 CreateDirectory("foo"); 295 CreateDirectory("foo/bar"); 296 CreateDirectory("foo/bar/baz"); 297 298 EnsureFileExists("foo/bar/hoge"); 299 TruncateFile("foo/bar/hoge", 10); 300 301 TestRequest(CreateFileSystemURL("foo/bar/")); 302 303 ASSERT_FALSE(request_->is_pending()); 304 EXPECT_EQ(1, delegate_->response_started_count()); 305 EXPECT_FALSE(delegate_->received_data_before_response()); 306 EXPECT_GT(delegate_->bytes_received(), 0); 307 308 std::istringstream in(delegate_->data_received()); 309 std::string line; 310 EXPECT_TRUE(!!std::getline(in, line)); 311 312 #if defined(OS_WIN) 313 EXPECT_EQ("<script>start(\"foo\\\\bar\");</script>", line); 314 #elif defined(OS_POSIX) 315 EXPECT_EQ("<script>start(\"/foo/bar\");</script>", line); 316 #endif 317 318 EXPECT_TRUE(!!std::getline(in, line)); 319 VerifyListingEntry(line, "hoge", "hoge", false, 10); 320 321 EXPECT_TRUE(!!std::getline(in, line)); 322 VerifyListingEntry(line, "baz", "baz", true, 0); 323 EXPECT_FALSE(!!std::getline(in, line)); 324 } 325 326 TEST_F(FileSystemDirURLRequestJobTest, InvalidURL) { 327 TestRequest(GURL("filesystem:/foo/bar/baz")); 328 ASSERT_FALSE(request_->is_pending()); 329 EXPECT_TRUE(delegate_->request_failed()); 330 ASSERT_FALSE(request_->status().is_success()); 331 EXPECT_EQ(net::ERR_INVALID_URL, request_->status().error()); 332 } 333 334 TEST_F(FileSystemDirURLRequestJobTest, NoSuchRoot) { 335 TestRequest(GURL("filesystem:http://remote/persistent/somedir/")); 336 ASSERT_FALSE(request_->is_pending()); 337 ASSERT_FALSE(request_->status().is_success()); 338 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 339 } 340 341 TEST_F(FileSystemDirURLRequestJobTest, NoSuchDirectory) { 342 TestRequest(CreateFileSystemURL("somedir/")); 343 ASSERT_FALSE(request_->is_pending()); 344 ASSERT_FALSE(request_->status().is_success()); 345 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 346 } 347 348 TEST_F(FileSystemDirURLRequestJobTest, Cancel) { 349 CreateDirectory("foo"); 350 TestRequestNoRun(CreateFileSystemURL("foo/")); 351 // Run StartAsync() and only StartAsync(). 352 base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release()); 353 base::RunLoop().RunUntilIdle(); 354 // If we get here, success! we didn't crash! 355 } 356 357 TEST_F(FileSystemDirURLRequestJobTest, Incognito) { 358 CreateDirectory("foo"); 359 360 scoped_refptr<FileSystemContext> file_system_context = 361 CreateIncognitoFileSystemContextForTesting(NULL, temp_dir_.path()); 362 363 TestRequestWithContext(CreateFileSystemURL("/"), 364 file_system_context.get()); 365 ASSERT_FALSE(request_->is_pending()); 366 ASSERT_TRUE(request_->status().is_success()); 367 368 std::istringstream in(delegate_->data_received()); 369 std::string line; 370 EXPECT_TRUE(!!std::getline(in, line)); 371 EXPECT_FALSE(!!std::getline(in, line)); 372 373 TestRequestWithContext(CreateFileSystemURL("foo"), 374 file_system_context.get()); 375 ASSERT_FALSE(request_->is_pending()); 376 ASSERT_FALSE(request_->status().is_success()); 377 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 378 } 379 380 TEST_F(FileSystemDirURLRequestJobTest, AutoMountDirectoryListing) { 381 base::FilePath mnt_point; 382 SetUpAutoMountContext(&mnt_point); 383 ASSERT_TRUE(base::CreateDirectory(mnt_point)); 384 ASSERT_TRUE(base::CreateDirectory(mnt_point.AppendASCII("foo"))); 385 ASSERT_EQ(10, 386 base::WriteFile(mnt_point.AppendASCII("bar"), "1234567890", 10)); 387 388 TestRequest(GURL("filesystem:http://automount/external/mnt_name")); 389 390 ASSERT_FALSE(request_->is_pending()); 391 EXPECT_EQ(1, delegate_->response_started_count()); 392 EXPECT_FALSE(delegate_->received_data_before_response()); 393 EXPECT_GT(delegate_->bytes_received(), 0); 394 395 std::istringstream in(delegate_->data_received()); 396 std::string line; 397 EXPECT_TRUE(!!std::getline(in, line)); // |line| contains the temp dir path. 398 399 // Result order is not guaranteed, so sort the results. 400 std::vector<std::string> listing_entries; 401 while (!!std::getline(in, line)) 402 listing_entries.push_back(line); 403 404 ASSERT_EQ(2U, listing_entries.size()); 405 std::sort(listing_entries.begin(), listing_entries.end()); 406 VerifyListingEntry(listing_entries[0], "bar", "bar", false, 10); 407 VerifyListingEntry(listing_entries[1], "foo", "foo", true, -1); 408 409 ASSERT_TRUE( 410 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem( 411 kValidExternalMountPoint)); 412 } 413 414 TEST_F(FileSystemDirURLRequestJobTest, AutoMountInvalidRoot) { 415 base::FilePath mnt_point; 416 SetUpAutoMountContext(&mnt_point); 417 TestRequest(GURL("filesystem:http://automount/external/invalid")); 418 419 ASSERT_FALSE(request_->is_pending()); 420 ASSERT_FALSE(request_->status().is_success()); 421 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 422 423 ASSERT_FALSE( 424 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem( 425 "invalid")); 426 } 427 428 TEST_F(FileSystemDirURLRequestJobTest, AutoMountNoHandler) { 429 base::FilePath mnt_point; 430 SetUpAutoMountContext(&mnt_point); 431 TestRequest(GURL("filesystem:http://noauto/external/mnt_name")); 432 433 ASSERT_FALSE(request_->is_pending()); 434 ASSERT_FALSE(request_->status().is_success()); 435 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 436 437 ASSERT_FALSE( 438 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem( 439 kValidExternalMountPoint)); 440 } 441 442 } // namespace (anonymous) 443 } // namespace content 444