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_url_request_job.h" 6 7#include <string> 8 9#include "base/bind.h" 10#include "base/files/file_path.h" 11#include "base/files/file_util.h" 12#include "base/files/scoped_temp_dir.h" 13#include "base/format_macros.h" 14#include "base/memory/scoped_vector.h" 15#include "base/memory/weak_ptr.h" 16#include "base/message_loop/message_loop.h" 17#include "base/message_loop/message_loop_proxy.h" 18#include "base/rand_util.h" 19#include "base/run_loop.h" 20#include "base/strings/string_piece.h" 21#include "base/strings/stringprintf.h" 22#include "base/strings/utf_string_conversions.h" 23#include "content/public/test/async_file_test_helper.h" 24#include "content/public/test/test_file_system_backend.h" 25#include "content/public/test/test_file_system_context.h" 26#include "net/base/load_flags.h" 27#include "net/base/mime_util.h" 28#include "net/base/net_errors.h" 29#include "net/base/net_util.h" 30#include "net/base/request_priority.h" 31#include "net/http/http_byte_range.h" 32#include "net/http/http_request_headers.h" 33#include "net/url_request/url_request.h" 34#include "net/url_request/url_request_context.h" 35#include "net/url_request/url_request_test_util.h" 36#include "storage/browser/fileapi/external_mount_points.h" 37#include "storage/browser/fileapi/file_system_context.h" 38#include "storage/browser/fileapi/file_system_file_util.h" 39#include "testing/gtest/include/gtest/gtest.h" 40 41using content::AsyncFileTestHelper; 42using storage::FileSystemContext; 43using storage::FileSystemURL; 44using storage::FileSystemURLRequestJob; 45 46namespace content { 47namespace { 48 49// We always use the TEMPORARY FileSystem in this test. 50const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/"; 51const char kTestFileData[] = "0123456789"; 52 53void FillBuffer(char* buffer, size_t len) { 54 base::RandBytes(buffer, len); 55} 56 57const char kValidExternalMountPoint[] = "mnt_name"; 58 59// An auto mounter that will try to mount anything for |storage_domain| = 60// "automount", but will only succeed for the mount point "mnt_name". 61bool TestAutoMountForURLRequest( 62 const net::URLRequest* /*url_request*/, 63 const storage::FileSystemURL& filesystem_url, 64 const std::string& storage_domain, 65 const base::Callback<void(base::File::Error result)>& callback) { 66 if (storage_domain != "automount") 67 return false; 68 std::vector<base::FilePath::StringType> components; 69 filesystem_url.path().GetComponents(&components); 70 std::string mount_point = base::FilePath(components[0]).AsUTF8Unsafe(); 71 72 if (mount_point == kValidExternalMountPoint) { 73 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( 74 kValidExternalMountPoint, 75 storage::kFileSystemTypeTest, 76 storage::FileSystemMountOption(), 77 base::FilePath()); 78 callback.Run(base::File::FILE_OK); 79 } else { 80 callback.Run(base::File::FILE_ERROR_NOT_FOUND); 81 } 82 return true; 83} 84 85class FileSystemURLRequestJobFactory : public net::URLRequestJobFactory { 86 public: 87 FileSystemURLRequestJobFactory(const std::string& storage_domain, 88 FileSystemContext* context) 89 : storage_domain_(storage_domain), file_system_context_(context) { 90 } 91 92 virtual net::URLRequestJob* MaybeCreateJobWithProtocolHandler( 93 const std::string& scheme, 94 net::URLRequest* request, 95 net::NetworkDelegate* network_delegate) const OVERRIDE { 96 return new storage::FileSystemURLRequestJob( 97 request, network_delegate, storage_domain_, file_system_context_); 98 } 99 100 virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE { 101 return true; 102 } 103 104 virtual bool IsHandledURL(const GURL& url) const OVERRIDE { 105 return true; 106 } 107 108 virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE { 109 return false; 110 } 111 112 private: 113 std::string storage_domain_; 114 FileSystemContext* file_system_context_; 115}; 116 117} // namespace 118 119class FileSystemURLRequestJobTest : public testing::Test { 120 protected: 121 FileSystemURLRequestJobTest() : weak_factory_(this) { 122 } 123 124 virtual void SetUp() OVERRIDE { 125 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 126 127 // We use the main thread so that we can get the root path synchronously. 128 // TODO(adamk): Run this on the FILE thread we've created as well. 129 file_system_context_ = 130 CreateFileSystemContextForTesting(NULL, temp_dir_.path()); 131 132 file_system_context_->OpenFileSystem( 133 GURL("http://remote/"), 134 storage::kFileSystemTypeTemporary, 135 storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, 136 base::Bind(&FileSystemURLRequestJobTest::OnOpenFileSystem, 137 weak_factory_.GetWeakPtr())); 138 base::RunLoop().RunUntilIdle(); 139 } 140 141 virtual void TearDown() OVERRIDE { 142 // FileReader posts a task to close the file in destructor. 143 base::RunLoop().RunUntilIdle(); 144 } 145 146 void SetUpAutoMountContext() { 147 base::FilePath mnt_point = temp_dir_.path().AppendASCII("auto_mount_dir"); 148 ASSERT_TRUE(base::CreateDirectory(mnt_point)); 149 150 ScopedVector<storage::FileSystemBackend> additional_providers; 151 additional_providers.push_back(new TestFileSystemBackend( 152 base::MessageLoopProxy::current().get(), mnt_point)); 153 154 std::vector<storage::URLRequestAutoMountHandler> handlers; 155 handlers.push_back(base::Bind(&TestAutoMountForURLRequest)); 156 157 file_system_context_ = CreateFileSystemContextWithAutoMountersForTesting( 158 NULL, additional_providers.Pass(), handlers, temp_dir_.path()); 159 160 ASSERT_EQ(static_cast<int>(sizeof(kTestFileData)) - 1, 161 base::WriteFile(mnt_point.AppendASCII("foo"), kTestFileData, 162 sizeof(kTestFileData) - 1)); 163 } 164 165 void OnOpenFileSystem(const GURL& root_url, 166 const std::string& name, 167 base::File::Error result) { 168 ASSERT_EQ(base::File::FILE_OK, result); 169 } 170 171 void TestRequestHelper(const GURL& url, 172 const net::HttpRequestHeaders* headers, 173 bool run_to_completion, 174 FileSystemContext* file_system_context) { 175 delegate_.reset(new net::TestDelegate()); 176 // Make delegate_ exit the MessageLoop when the request is done. 177 delegate_->set_quit_on_complete(true); 178 delegate_->set_quit_on_redirect(true); 179 180 job_factory_.reset(new FileSystemURLRequestJobFactory( 181 url.GetOrigin().host(), file_system_context)); 182 empty_context_.set_job_factory(job_factory_.get()); 183 184 request_ = empty_context_.CreateRequest( 185 url, net::DEFAULT_PRIORITY, delegate_.get(), NULL); 186 if (headers) 187 request_->SetExtraRequestHeaders(*headers); 188 189 request_->Start(); 190 ASSERT_TRUE(request_->is_pending()); // verify that we're starting async 191 if (run_to_completion) 192 base::MessageLoop::current()->Run(); 193 } 194 195 void TestRequest(const GURL& url) { 196 TestRequestHelper(url, NULL, true, file_system_context_.get()); 197 } 198 199 void TestRequestWithContext(const GURL& url, 200 FileSystemContext* file_system_context) { 201 TestRequestHelper(url, NULL, true, file_system_context); 202 } 203 204 void TestRequestWithHeaders(const GURL& url, 205 const net::HttpRequestHeaders* headers) { 206 TestRequestHelper(url, headers, true, file_system_context_.get()); 207 } 208 209 void TestRequestNoRun(const GURL& url) { 210 TestRequestHelper(url, NULL, false, file_system_context_.get()); 211 } 212 213 void CreateDirectory(const base::StringPiece& dir_name) { 214 FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( 215 GURL("http://remote"), 216 storage::kFileSystemTypeTemporary, 217 base::FilePath().AppendASCII(dir_name)); 218 ASSERT_EQ( 219 base::File::FILE_OK, 220 AsyncFileTestHelper::CreateDirectory(file_system_context_.get(), url)); 221 } 222 223 void WriteFile(const base::StringPiece& file_name, 224 const char* buf, int buf_size) { 225 FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( 226 GURL("http://remote"), 227 storage::kFileSystemTypeTemporary, 228 base::FilePath().AppendASCII(file_name)); 229 ASSERT_EQ(base::File::FILE_OK, 230 AsyncFileTestHelper::CreateFileWithData( 231 file_system_context_.get(), url, buf, buf_size)); 232 } 233 234 GURL CreateFileSystemURL(const std::string& path) { 235 return GURL(kFileSystemURLPrefix + path); 236 } 237 238 // Put the message loop at the top, so that it's the last thing deleted. 239 base::MessageLoopForIO message_loop_; 240 241 base::ScopedTempDir temp_dir_; 242 scoped_refptr<storage::FileSystemContext> file_system_context_; 243 base::WeakPtrFactory<FileSystemURLRequestJobTest> weak_factory_; 244 245 net::URLRequestContext empty_context_; 246 scoped_ptr<FileSystemURLRequestJobFactory> job_factory_; 247 248 // NOTE: order matters, request must die before delegate 249 scoped_ptr<net::TestDelegate> delegate_; 250 scoped_ptr<net::URLRequest> request_; 251}; 252 253namespace { 254 255TEST_F(FileSystemURLRequestJobTest, FileTest) { 256 WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); 257 TestRequest(CreateFileSystemURL("file1.dat")); 258 259 ASSERT_FALSE(request_->is_pending()); 260 EXPECT_EQ(1, delegate_->response_started_count()); 261 EXPECT_FALSE(delegate_->received_data_before_response()); 262 EXPECT_EQ(kTestFileData, delegate_->data_received()); 263 EXPECT_EQ(200, request_->GetResponseCode()); 264 std::string cache_control; 265 request_->GetResponseHeaderByName("cache-control", &cache_control); 266 EXPECT_EQ("no-cache", cache_control); 267} 268 269TEST_F(FileSystemURLRequestJobTest, FileTestFullSpecifiedRange) { 270 const size_t buffer_size = 4000; 271 scoped_ptr<char[]> buffer(new char[buffer_size]); 272 FillBuffer(buffer.get(), buffer_size); 273 WriteFile("bigfile", buffer.get(), buffer_size); 274 275 const size_t first_byte_position = 500; 276 const size_t last_byte_position = buffer_size - first_byte_position; 277 std::string partial_buffer_string(buffer.get() + first_byte_position, 278 buffer.get() + last_byte_position + 1); 279 280 net::HttpRequestHeaders headers; 281 headers.SetHeader( 282 net::HttpRequestHeaders::kRange, 283 net::HttpByteRange::Bounded( 284 first_byte_position, last_byte_position).GetHeaderValue()); 285 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers); 286 287 ASSERT_FALSE(request_->is_pending()); 288 EXPECT_EQ(1, delegate_->response_started_count()); 289 EXPECT_FALSE(delegate_->received_data_before_response()); 290 EXPECT_TRUE(partial_buffer_string == delegate_->data_received()); 291} 292 293TEST_F(FileSystemURLRequestJobTest, FileTestHalfSpecifiedRange) { 294 const size_t buffer_size = 4000; 295 scoped_ptr<char[]> buffer(new char[buffer_size]); 296 FillBuffer(buffer.get(), buffer_size); 297 WriteFile("bigfile", buffer.get(), buffer_size); 298 299 const size_t first_byte_position = 500; 300 std::string partial_buffer_string(buffer.get() + first_byte_position, 301 buffer.get() + buffer_size); 302 303 net::HttpRequestHeaders headers; 304 headers.SetHeader( 305 net::HttpRequestHeaders::kRange, 306 net::HttpByteRange::RightUnbounded(first_byte_position).GetHeaderValue()); 307 TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers); 308 ASSERT_FALSE(request_->is_pending()); 309 EXPECT_EQ(1, delegate_->response_started_count()); 310 EXPECT_FALSE(delegate_->received_data_before_response()); 311 // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed. 312 EXPECT_TRUE(partial_buffer_string == delegate_->data_received()); 313} 314 315TEST_F(FileSystemURLRequestJobTest, FileTestMultipleRangesNotSupported) { 316 WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); 317 net::HttpRequestHeaders headers; 318 headers.SetHeader(net::HttpRequestHeaders::kRange, 319 "bytes=0-5,10-200,200-300"); 320 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers); 321 EXPECT_TRUE(delegate_->request_failed()); 322 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, 323 request_->status().error()); 324} 325 326TEST_F(FileSystemURLRequestJobTest, RangeOutOfBounds) { 327 WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); 328 net::HttpRequestHeaders headers; 329 headers.SetHeader( 330 net::HttpRequestHeaders::kRange, 331 net::HttpByteRange::Bounded(500, 1000).GetHeaderValue()); 332 TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers); 333 334 ASSERT_FALSE(request_->is_pending()); 335 EXPECT_TRUE(delegate_->request_failed()); 336 EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, 337 request_->status().error()); 338} 339 340TEST_F(FileSystemURLRequestJobTest, FileDirRedirect) { 341 CreateDirectory("dir"); 342 TestRequest(CreateFileSystemURL("dir")); 343 344 EXPECT_EQ(1, delegate_->received_redirect_count()); 345 EXPECT_TRUE(request_->status().is_success()); 346 EXPECT_FALSE(delegate_->request_failed()); 347 348 // We've deferred the redirect; now cancel the request to avoid following it. 349 request_->Cancel(); 350 base::MessageLoop::current()->Run(); 351} 352 353TEST_F(FileSystemURLRequestJobTest, InvalidURL) { 354 TestRequest(GURL("filesystem:/foo/bar/baz")); 355 ASSERT_FALSE(request_->is_pending()); 356 EXPECT_TRUE(delegate_->request_failed()); 357 EXPECT_EQ(net::ERR_INVALID_URL, request_->status().error()); 358} 359 360TEST_F(FileSystemURLRequestJobTest, NoSuchRoot) { 361 TestRequest(GURL("filesystem:http://remote/persistent/somefile")); 362 ASSERT_FALSE(request_->is_pending()); 363 EXPECT_TRUE(delegate_->request_failed()); 364 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 365} 366 367TEST_F(FileSystemURLRequestJobTest, NoSuchFile) { 368 TestRequest(CreateFileSystemURL("somefile")); 369 ASSERT_FALSE(request_->is_pending()); 370 EXPECT_TRUE(delegate_->request_failed()); 371 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 372} 373 374TEST_F(FileSystemURLRequestJobTest, Cancel) { 375 WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); 376 TestRequestNoRun(CreateFileSystemURL("file1.dat")); 377 378 // Run StartAsync() and only StartAsync(). 379 base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release()); 380 base::RunLoop().RunUntilIdle(); 381 // If we get here, success! we didn't crash! 382} 383 384TEST_F(FileSystemURLRequestJobTest, GetMimeType) { 385 const char kFilename[] = "hoge.html"; 386 387 std::string mime_type_direct; 388 base::FilePath::StringType extension = 389 base::FilePath().AppendASCII(kFilename).Extension(); 390 if (!extension.empty()) 391 extension = extension.substr(1); 392 EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension( 393 extension, &mime_type_direct)); 394 395 TestRequest(CreateFileSystemURL(kFilename)); 396 397 std::string mime_type_from_job; 398 request_->GetMimeType(&mime_type_from_job); 399 EXPECT_EQ(mime_type_direct, mime_type_from_job); 400} 401 402TEST_F(FileSystemURLRequestJobTest, Incognito) { 403 WriteFile("file", kTestFileData, arraysize(kTestFileData) - 1); 404 405 // Creates a new filesystem context for incognito mode. 406 scoped_refptr<FileSystemContext> file_system_context = 407 CreateIncognitoFileSystemContextForTesting(NULL, temp_dir_.path()); 408 409 // The request should return NOT_FOUND error if it's in incognito mode. 410 TestRequestWithContext(CreateFileSystemURL("file"), 411 file_system_context.get()); 412 ASSERT_FALSE(request_->is_pending()); 413 EXPECT_TRUE(delegate_->request_failed()); 414 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 415 416 // Make sure it returns success with regular (non-incognito) context. 417 TestRequest(CreateFileSystemURL("file")); 418 ASSERT_FALSE(request_->is_pending()); 419 EXPECT_EQ(kTestFileData, delegate_->data_received()); 420 EXPECT_EQ(200, request_->GetResponseCode()); 421} 422 423TEST_F(FileSystemURLRequestJobTest, AutoMountFileTest) { 424 SetUpAutoMountContext(); 425 TestRequest(GURL("filesystem:http://automount/external/mnt_name/foo")); 426 427 ASSERT_FALSE(request_->is_pending()); 428 EXPECT_EQ(1, delegate_->response_started_count()); 429 EXPECT_FALSE(delegate_->received_data_before_response()); 430 EXPECT_EQ(kTestFileData, delegate_->data_received()); 431 EXPECT_EQ(200, request_->GetResponseCode()); 432 std::string cache_control; 433 request_->GetResponseHeaderByName("cache-control", &cache_control); 434 EXPECT_EQ("no-cache", cache_control); 435 436 ASSERT_TRUE( 437 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem( 438 kValidExternalMountPoint)); 439} 440 441TEST_F(FileSystemURLRequestJobTest, AutoMountInvalidRoot) { 442 SetUpAutoMountContext(); 443 TestRequest(GURL("filesystem:http://automount/external/invalid/foo")); 444 445 ASSERT_FALSE(request_->is_pending()); 446 EXPECT_TRUE(delegate_->request_failed()); 447 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 448 449 ASSERT_FALSE( 450 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem( 451 "invalid")); 452} 453 454TEST_F(FileSystemURLRequestJobTest, AutoMountNoHandler) { 455 SetUpAutoMountContext(); 456 TestRequest(GURL("filesystem:http://noauto/external/mnt_name/foo")); 457 458 ASSERT_FALSE(request_->is_pending()); 459 EXPECT_TRUE(delegate_->request_failed()); 460 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); 461 462 ASSERT_FALSE( 463 storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem( 464 kValidExternalMountPoint)); 465} 466 467} // namespace 468} // namespace content 469