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