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 "base/basictypes.h" 6#include "base/bind.h" 7#include "base/files/file_path.h" 8#include "base/files/file_util.h" 9#include "base/files/scoped_temp_dir.h" 10#include "base/memory/ref_counted.h" 11#include "base/memory/scoped_ptr.h" 12#include "base/numerics/safe_conversions.h" 13#include "base/run_loop.h" 14#include "base/time/time.h" 15#include "content/browser/fileapi/mock_url_request_delegate.h" 16#include "content/public/test/async_file_test_helper.h" 17#include "content/public/test/test_file_system_context.h" 18#include "net/base/request_priority.h" 19#include "net/http/http_byte_range.h" 20#include "net/http/http_request_headers.h" 21#include "net/http/http_response_headers.h" 22#include "net/url_request/url_request.h" 23#include "net/url_request/url_request_context.h" 24#include "net/url_request/url_request_job_factory_impl.h" 25#include "storage/browser/blob/blob_url_request_job.h" 26#include "storage/browser/fileapi/file_system_context.h" 27#include "storage/browser/fileapi/file_system_operation_context.h" 28#include "storage/browser/fileapi/file_system_url.h" 29#include "storage/common/blob/blob_data.h" 30#include "testing/gtest/include/gtest/gtest.h" 31 32using storage::BlobData; 33using storage::BlobURLRequestJob; 34 35namespace content { 36 37namespace { 38 39const int kBufferSize = 1024; 40const char kTestData1[] = "Hello"; 41const char kTestData2[] = "Here it is data."; 42const char kTestFileData1[] = "0123456789"; 43const char kTestFileData2[] = "This is sample file."; 44const char kTestFileSystemFileData1[] = "abcdefghijklmnop"; 45const char kTestFileSystemFileData2[] = "File system file test data."; 46const char kTestContentType[] = "foo/bar"; 47const char kTestContentDisposition[] = "attachment; filename=foo.txt"; 48 49const char kFileSystemURLOrigin[] = "http://remote"; 50const storage::FileSystemType kFileSystemType = 51 storage::kFileSystemTypeTemporary; 52 53} // namespace 54 55class BlobURLRequestJobTest : public testing::Test { 56 public: 57 // A simple ProtocolHandler implementation to create BlobURLRequestJob. 58 class MockProtocolHandler : 59 public net::URLRequestJobFactory::ProtocolHandler { 60 public: 61 MockProtocolHandler(BlobURLRequestJobTest* test) : test_(test) {} 62 63 // net::URLRequestJobFactory::ProtocolHandler override. 64 virtual net::URLRequestJob* MaybeCreateJob( 65 net::URLRequest* request, 66 net::NetworkDelegate* network_delegate) const OVERRIDE { 67 return new BlobURLRequestJob(request, 68 network_delegate, 69 test_->blob_data_.get(), 70 test_->file_system_context_.get(), 71 base::MessageLoopProxy::current().get()); 72 } 73 74 private: 75 BlobURLRequestJobTest* test_; 76 }; 77 78 BlobURLRequestJobTest() 79 : blob_data_(new BlobData()), 80 expected_status_code_(0) {} 81 82 virtual void SetUp() { 83 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 84 85 temp_file1_ = temp_dir_.path().AppendASCII("BlobFile1.dat"); 86 ASSERT_EQ(static_cast<int>(arraysize(kTestFileData1) - 1), 87 base::WriteFile(temp_file1_, kTestFileData1, 88 arraysize(kTestFileData1) - 1)); 89 base::File::Info file_info1; 90 base::GetFileInfo(temp_file1_, &file_info1); 91 temp_file_modification_time1_ = file_info1.last_modified; 92 93 temp_file2_ = temp_dir_.path().AppendASCII("BlobFile2.dat"); 94 ASSERT_EQ(static_cast<int>(arraysize(kTestFileData2) - 1), 95 base::WriteFile(temp_file2_, kTestFileData2, 96 arraysize(kTestFileData2) - 1)); 97 base::File::Info file_info2; 98 base::GetFileInfo(temp_file2_, &file_info2); 99 temp_file_modification_time2_ = file_info2.last_modified; 100 101 url_request_job_factory_.SetProtocolHandler("blob", 102 new MockProtocolHandler(this)); 103 url_request_context_.set_job_factory(&url_request_job_factory_); 104 } 105 106 virtual void TearDown() { 107 } 108 109 void SetUpFileSystem() { 110 // Prepare file system. 111 file_system_context_ = CreateFileSystemContextForTesting( 112 NULL, temp_dir_.path()); 113 114 file_system_context_->OpenFileSystem( 115 GURL(kFileSystemURLOrigin), 116 kFileSystemType, 117 storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, 118 base::Bind(&BlobURLRequestJobTest::OnValidateFileSystem, 119 base::Unretained(this))); 120 base::RunLoop().RunUntilIdle(); 121 ASSERT_TRUE(file_system_root_url_.is_valid()); 122 123 // Prepare files on file system. 124 const char kFilename1[] = "FileSystemFile1.dat"; 125 temp_file_system_file1_ = GetFileSystemURL(kFilename1); 126 WriteFileSystemFile(kFilename1, kTestFileSystemFileData1, 127 arraysize(kTestFileSystemFileData1) - 1, 128 &temp_file_system_file_modification_time1_); 129 const char kFilename2[] = "FileSystemFile2.dat"; 130 temp_file_system_file2_ = GetFileSystemURL(kFilename2); 131 WriteFileSystemFile(kFilename2, kTestFileSystemFileData2, 132 arraysize(kTestFileSystemFileData2) - 1, 133 &temp_file_system_file_modification_time2_); 134 } 135 136 GURL GetFileSystemURL(const std::string& filename) { 137 return GURL(file_system_root_url_.spec() + filename); 138 } 139 140 void WriteFileSystemFile(const std::string& filename, 141 const char* buf, int buf_size, 142 base::Time* modification_time) { 143 storage::FileSystemURL url = 144 file_system_context_->CreateCrackedFileSystemURL( 145 GURL(kFileSystemURLOrigin), 146 kFileSystemType, 147 base::FilePath().AppendASCII(filename)); 148 149 ASSERT_EQ(base::File::FILE_OK, 150 content::AsyncFileTestHelper::CreateFileWithData( 151 file_system_context_.get(), url, buf, buf_size)); 152 153 base::File::Info file_info; 154 ASSERT_EQ(base::File::FILE_OK, 155 content::AsyncFileTestHelper::GetMetadata( 156 file_system_context_.get(), url, &file_info)); 157 if (modification_time) 158 *modification_time = file_info.last_modified; 159 } 160 161 void OnValidateFileSystem(const GURL& root, 162 const std::string& name, 163 base::File::Error result) { 164 ASSERT_EQ(base::File::FILE_OK, result); 165 ASSERT_TRUE(root.is_valid()); 166 file_system_root_url_ = root; 167 } 168 169 void TestSuccessNonrangeRequest(const std::string& expected_response, 170 int64 expected_content_length) { 171 expected_status_code_ = 200; 172 expected_response_ = expected_response; 173 TestRequest("GET", net::HttpRequestHeaders()); 174 EXPECT_EQ(expected_content_length, 175 request_->response_headers()->GetContentLength()); 176 } 177 178 void TestErrorRequest(int expected_status_code) { 179 expected_status_code_ = expected_status_code; 180 expected_response_ = ""; 181 TestRequest("GET", net::HttpRequestHeaders()); 182 } 183 184 void TestRequest(const std::string& method, 185 const net::HttpRequestHeaders& extra_headers) { 186 request_ = url_request_context_.CreateRequest( 187 GURL("blob:blah"), net::DEFAULT_PRIORITY, &url_request_delegate_, NULL); 188 request_->set_method(method); 189 if (!extra_headers.IsEmpty()) 190 request_->SetExtraRequestHeaders(extra_headers); 191 request_->Start(); 192 193 base::MessageLoop::current()->Run(); 194 195 // Verify response. 196 EXPECT_TRUE(request_->status().is_success()); 197 EXPECT_EQ(expected_status_code_, 198 request_->response_headers()->response_code()); 199 EXPECT_EQ(expected_response_, url_request_delegate_.response_data()); 200 } 201 202 void BuildComplicatedData(std::string* expected_result) { 203 blob_data_->AppendData(kTestData1 + 1, 2); 204 blob_data_->AppendFile(temp_file1_, 2, 3, temp_file_modification_time1_); 205 blob_data_->AppendFileSystemFile(temp_file_system_file1_, 3, 4, 206 temp_file_system_file_modification_time1_); 207 blob_data_->AppendData(kTestData2 + 4, 5); 208 blob_data_->AppendFile(temp_file2_, 5, 6, temp_file_modification_time2_); 209 blob_data_->AppendFileSystemFile(temp_file_system_file2_, 6, 7, 210 temp_file_system_file_modification_time2_); 211 *expected_result = std::string(kTestData1 + 1, 2); 212 *expected_result += std::string(kTestFileData1 + 2, 3); 213 *expected_result += std::string(kTestFileSystemFileData1 + 3, 4); 214 *expected_result += std::string(kTestData2 + 4, 5); 215 *expected_result += std::string(kTestFileData2 + 5, 6); 216 *expected_result += std::string(kTestFileSystemFileData2 + 6, 7); 217 } 218 219 // This only works if all the Blob items have a definite pre-computed length. 220 // Otherwise, this will fail a CHECK. 221 int64 GetTotalBlobLength() const { 222 int64 total = 0; 223 const std::vector<BlobData::Item>& items = blob_data_->items(); 224 for (std::vector<BlobData::Item>::const_iterator it = items.begin(); 225 it != items.end(); ++it) { 226 int64 length = base::checked_cast<int64>(it->length()); 227 CHECK(length <= kint64max - total); 228 total += length; 229 } 230 return total; 231 } 232 233 protected: 234 base::ScopedTempDir temp_dir_; 235 base::FilePath temp_file1_; 236 base::FilePath temp_file2_; 237 base::Time temp_file_modification_time1_; 238 base::Time temp_file_modification_time2_; 239 GURL file_system_root_url_; 240 GURL temp_file_system_file1_; 241 GURL temp_file_system_file2_; 242 base::Time temp_file_system_file_modification_time1_; 243 base::Time temp_file_system_file_modification_time2_; 244 245 base::MessageLoopForIO message_loop_; 246 scoped_refptr<storage::FileSystemContext> file_system_context_; 247 scoped_refptr<BlobData> blob_data_; 248 net::URLRequestJobFactoryImpl url_request_job_factory_; 249 net::URLRequestContext url_request_context_; 250 MockURLRequestDelegate url_request_delegate_; 251 scoped_ptr<net::URLRequest> request_; 252 253 int expected_status_code_; 254 std::string expected_response_; 255}; 256 257TEST_F(BlobURLRequestJobTest, TestGetSimpleDataRequest) { 258 blob_data_->AppendData(kTestData1); 259 TestSuccessNonrangeRequest(kTestData1, arraysize(kTestData1) - 1); 260} 261 262TEST_F(BlobURLRequestJobTest, TestGetSimpleFileRequest) { 263 blob_data_->AppendFile(temp_file1_, 0, kuint64max, base::Time()); 264 TestSuccessNonrangeRequest(kTestFileData1, arraysize(kTestFileData1) - 1); 265} 266 267TEST_F(BlobURLRequestJobTest, TestGetLargeFileRequest) { 268 base::FilePath large_temp_file = 269 temp_dir_.path().AppendASCII("LargeBlob.dat"); 270 std::string large_data; 271 large_data.reserve(kBufferSize * 5); 272 for (int i = 0; i < kBufferSize * 5; ++i) 273 large_data.append(1, static_cast<char>(i % 256)); 274 ASSERT_EQ(static_cast<int>(large_data.size()), 275 base::WriteFile(large_temp_file, large_data.data(), 276 large_data.size())); 277 blob_data_->AppendFile(large_temp_file, 0, kuint64max, base::Time()); 278 TestSuccessNonrangeRequest(large_data, large_data.size()); 279} 280 281TEST_F(BlobURLRequestJobTest, TestGetNonExistentFileRequest) { 282 base::FilePath non_existent_file = 283 temp_file1_.InsertBeforeExtension(FILE_PATH_LITERAL("-na")); 284 blob_data_->AppendFile(non_existent_file, 0, kuint64max, base::Time()); 285 TestErrorRequest(404); 286} 287 288TEST_F(BlobURLRequestJobTest, TestGetChangedFileRequest) { 289 base::Time old_time = 290 temp_file_modification_time1_ - base::TimeDelta::FromSeconds(10); 291 blob_data_->AppendFile(temp_file1_, 0, 3, old_time); 292 TestErrorRequest(404); 293} 294 295TEST_F(BlobURLRequestJobTest, TestGetSlicedFileRequest) { 296 blob_data_->AppendFile(temp_file1_, 2, 4, temp_file_modification_time1_); 297 std::string result(kTestFileData1 + 2, 4); 298 TestSuccessNonrangeRequest(result, 4); 299} 300 301TEST_F(BlobURLRequestJobTest, TestGetSimpleFileSystemFileRequest) { 302 SetUpFileSystem(); 303 blob_data_->AppendFileSystemFile(temp_file_system_file1_, 0, kuint64max, 304 base::Time()); 305 TestSuccessNonrangeRequest(kTestFileSystemFileData1, 306 arraysize(kTestFileSystemFileData1) - 1); 307} 308 309TEST_F(BlobURLRequestJobTest, TestGetLargeFileSystemFileRequest) { 310 SetUpFileSystem(); 311 std::string large_data; 312 large_data.reserve(kBufferSize * 5); 313 for (int i = 0; i < kBufferSize * 5; ++i) 314 large_data.append(1, static_cast<char>(i % 256)); 315 316 const char kFilename[] = "LargeBlob.dat"; 317 WriteFileSystemFile(kFilename, large_data.data(), large_data.size(), NULL); 318 319 blob_data_->AppendFileSystemFile(GetFileSystemURL(kFilename), 0, kuint64max, 320 base::Time()); 321 TestSuccessNonrangeRequest(large_data, large_data.size()); 322} 323 324TEST_F(BlobURLRequestJobTest, TestGetNonExistentFileSystemFileRequest) { 325 SetUpFileSystem(); 326 GURL non_existent_file = GetFileSystemURL("non-existent.dat"); 327 blob_data_->AppendFileSystemFile(non_existent_file, 0, kuint64max, 328 base::Time()); 329 TestErrorRequest(404); 330} 331 332TEST_F(BlobURLRequestJobTest, TestGetChangedFileSystemFileRequest) { 333 SetUpFileSystem(); 334 base::Time old_time = 335 temp_file_system_file_modification_time1_ - 336 base::TimeDelta::FromSeconds(10); 337 blob_data_->AppendFileSystemFile(temp_file_system_file1_, 0, 3, old_time); 338 TestErrorRequest(404); 339} 340 341TEST_F(BlobURLRequestJobTest, TestGetSlicedFileSystemFileRequest) { 342 SetUpFileSystem(); 343 blob_data_->AppendFileSystemFile(temp_file_system_file1_, 2, 4, 344 temp_file_system_file_modification_time1_); 345 std::string result(kTestFileSystemFileData1 + 2, 4); 346 TestSuccessNonrangeRequest(result, 4); 347} 348 349TEST_F(BlobURLRequestJobTest, TestGetComplicatedDataAndFileRequest) { 350 SetUpFileSystem(); 351 std::string result; 352 BuildComplicatedData(&result); 353 TestSuccessNonrangeRequest(result, GetTotalBlobLength()); 354} 355 356TEST_F(BlobURLRequestJobTest, TestGetRangeRequest1) { 357 SetUpFileSystem(); 358 std::string result; 359 BuildComplicatedData(&result); 360 net::HttpRequestHeaders extra_headers; 361 extra_headers.SetHeader(net::HttpRequestHeaders::kRange, 362 net::HttpByteRange::Bounded(5, 10).GetHeaderValue()); 363 expected_status_code_ = 206; 364 expected_response_ = result.substr(5, 10 - 5 + 1); 365 TestRequest("GET", extra_headers); 366 367 EXPECT_EQ(6, request_->response_headers()->GetContentLength()); 368 369 int64 first = 0, last = 0, length = 0; 370 EXPECT_TRUE( 371 request_->response_headers()->GetContentRange(&first, &last, &length)); 372 EXPECT_EQ(5, first); 373 EXPECT_EQ(10, last); 374 EXPECT_EQ(GetTotalBlobLength(), length); 375} 376 377TEST_F(BlobURLRequestJobTest, TestGetRangeRequest2) { 378 SetUpFileSystem(); 379 std::string result; 380 BuildComplicatedData(&result); 381 net::HttpRequestHeaders extra_headers; 382 extra_headers.SetHeader(net::HttpRequestHeaders::kRange, 383 net::HttpByteRange::Suffix(10).GetHeaderValue()); 384 expected_status_code_ = 206; 385 expected_response_ = result.substr(result.length() - 10); 386 TestRequest("GET", extra_headers); 387 388 EXPECT_EQ(10, request_->response_headers()->GetContentLength()); 389 390 int64 total = GetTotalBlobLength(); 391 int64 first = 0, last = 0, length = 0; 392 EXPECT_TRUE( 393 request_->response_headers()->GetContentRange(&first, &last, &length)); 394 EXPECT_EQ(total - 10, first); 395 EXPECT_EQ(total - 1, last); 396 EXPECT_EQ(total, length); 397} 398 399TEST_F(BlobURLRequestJobTest, TestExtraHeaders) { 400 blob_data_->set_content_type(kTestContentType); 401 blob_data_->set_content_disposition(kTestContentDisposition); 402 blob_data_->AppendData(kTestData1); 403 expected_status_code_ = 200; 404 expected_response_ = kTestData1; 405 TestRequest("GET", net::HttpRequestHeaders()); 406 407 std::string content_type; 408 EXPECT_TRUE(request_->response_headers()->GetMimeType(&content_type)); 409 EXPECT_EQ(kTestContentType, content_type); 410 void* iter = NULL; 411 std::string content_disposition; 412 EXPECT_TRUE(request_->response_headers()->EnumerateHeader( 413 &iter, "Content-Disposition", &content_disposition)); 414 EXPECT_EQ(kTestContentDisposition, content_disposition); 415} 416 417} // namespace content 418