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/file_util.h" 8 #include "base/files/file_path.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 "testing/gtest/include/gtest/gtest.h" 26 #include "webkit/browser/blob/blob_url_request_job.h" 27 #include "webkit/browser/fileapi/file_system_context.h" 28 #include "webkit/browser/fileapi/file_system_operation_context.h" 29 #include "webkit/browser/fileapi/file_system_url.h" 30 #include "webkit/common/blob/blob_data.h" 31 32 using webkit_blob::BlobData; 33 using webkit_blob::BlobURLRequestJob; 34 35 namespace content { 36 37 namespace { 38 39 const int kBufferSize = 1024; 40 const char kTestData1[] = "Hello"; 41 const char kTestData2[] = "Here it is data."; 42 const char kTestFileData1[] = "0123456789"; 43 const char kTestFileData2[] = "This is sample file."; 44 const char kTestFileSystemFileData1[] = "abcdefghijklmnop"; 45 const char kTestFileSystemFileData2[] = "File system file test data."; 46 const char kTestContentType[] = "foo/bar"; 47 const char kTestContentDisposition[] = "attachment; filename=foo.txt"; 48 49 const char kFileSystemURLOrigin[] = "http://remote"; 50 const fileapi::FileSystemType kFileSystemType = 51 fileapi::kFileSystemTypeTemporary; 52 53 } // namespace 54 55 class 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 fileapi::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 fileapi::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_, 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_, 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<fileapi::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 257 TEST_F(BlobURLRequestJobTest, TestGetSimpleDataRequest) { 258 blob_data_->AppendData(kTestData1); 259 TestSuccessNonrangeRequest(kTestData1, arraysize(kTestData1) - 1); 260 } 261 262 TEST_F(BlobURLRequestJobTest, TestGetSimpleFileRequest) { 263 blob_data_->AppendFile(temp_file1_, 0, -1, base::Time()); 264 TestSuccessNonrangeRequest(kTestFileData1, arraysize(kTestFileData1) - 1); 265 } 266 267 TEST_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, -1, base::Time()); 278 TestSuccessNonrangeRequest(large_data, large_data.size()); 279 } 280 281 TEST_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, -1, base::Time()); 285 TestErrorRequest(404); 286 } 287 288 TEST_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 295 TEST_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 301 TEST_F(BlobURLRequestJobTest, TestGetSimpleFileSystemFileRequest) { 302 SetUpFileSystem(); 303 blob_data_->AppendFileSystemFile(temp_file_system_file1_, 0, -1, 304 base::Time()); 305 TestSuccessNonrangeRequest(kTestFileSystemFileData1, 306 arraysize(kTestFileSystemFileData1) - 1); 307 } 308 309 TEST_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, -1, 320 base::Time()); 321 TestSuccessNonrangeRequest(large_data, large_data.size()); 322 } 323 324 TEST_F(BlobURLRequestJobTest, TestGetNonExistentFileSystemFileRequest) { 325 SetUpFileSystem(); 326 GURL non_existent_file = GetFileSystemURL("non-existent.dat"); 327 blob_data_->AppendFileSystemFile(non_existent_file, 0, -1, base::Time()); 328 TestErrorRequest(404); 329 } 330 331 TEST_F(BlobURLRequestJobTest, TestGetChangedFileSystemFileRequest) { 332 SetUpFileSystem(); 333 base::Time old_time = 334 temp_file_system_file_modification_time1_ - 335 base::TimeDelta::FromSeconds(10); 336 blob_data_->AppendFileSystemFile(temp_file_system_file1_, 0, 3, old_time); 337 TestErrorRequest(404); 338 } 339 340 TEST_F(BlobURLRequestJobTest, TestGetSlicedFileSystemFileRequest) { 341 SetUpFileSystem(); 342 blob_data_->AppendFileSystemFile(temp_file_system_file1_, 2, 4, 343 temp_file_system_file_modification_time1_); 344 std::string result(kTestFileSystemFileData1 + 2, 4); 345 TestSuccessNonrangeRequest(result, 4); 346 } 347 348 TEST_F(BlobURLRequestJobTest, TestGetComplicatedDataAndFileRequest) { 349 SetUpFileSystem(); 350 std::string result; 351 BuildComplicatedData(&result); 352 TestSuccessNonrangeRequest(result, GetTotalBlobLength()); 353 } 354 355 TEST_F(BlobURLRequestJobTest, TestGetRangeRequest1) { 356 SetUpFileSystem(); 357 std::string result; 358 BuildComplicatedData(&result); 359 net::HttpRequestHeaders extra_headers; 360 extra_headers.SetHeader(net::HttpRequestHeaders::kRange, 361 net::HttpByteRange::Bounded(5, 10).GetHeaderValue()); 362 expected_status_code_ = 206; 363 expected_response_ = result.substr(5, 10 - 5 + 1); 364 TestRequest("GET", extra_headers); 365 366 EXPECT_EQ(6, request_->response_headers()->GetContentLength()); 367 368 int64 first = 0, last = 0, length = 0; 369 EXPECT_TRUE( 370 request_->response_headers()->GetContentRange(&first, &last, &length)); 371 EXPECT_EQ(5, first); 372 EXPECT_EQ(10, last); 373 EXPECT_EQ(GetTotalBlobLength(), length); 374 } 375 376 TEST_F(BlobURLRequestJobTest, TestGetRangeRequest2) { 377 SetUpFileSystem(); 378 std::string result; 379 BuildComplicatedData(&result); 380 net::HttpRequestHeaders extra_headers; 381 extra_headers.SetHeader(net::HttpRequestHeaders::kRange, 382 net::HttpByteRange::Suffix(10).GetHeaderValue()); 383 expected_status_code_ = 206; 384 expected_response_ = result.substr(result.length() - 10); 385 TestRequest("GET", extra_headers); 386 387 EXPECT_EQ(10, request_->response_headers()->GetContentLength()); 388 389 int64 total = GetTotalBlobLength(); 390 int64 first = 0, last = 0, length = 0; 391 EXPECT_TRUE( 392 request_->response_headers()->GetContentRange(&first, &last, &length)); 393 EXPECT_EQ(total - 10, first); 394 EXPECT_EQ(total - 1, last); 395 EXPECT_EQ(total, length); 396 } 397 398 TEST_F(BlobURLRequestJobTest, TestExtraHeaders) { 399 blob_data_->set_content_type(kTestContentType); 400 blob_data_->set_content_disposition(kTestContentDisposition); 401 blob_data_->AppendData(kTestData1); 402 expected_status_code_ = 200; 403 expected_response_ = kTestData1; 404 TestRequest("GET", net::HttpRequestHeaders()); 405 406 std::string content_type; 407 EXPECT_TRUE(request_->response_headers()->GetMimeType(&content_type)); 408 EXPECT_EQ(kTestContentType, content_type); 409 void* iter = NULL; 410 std::string content_disposition; 411 EXPECT_TRUE(request_->response_headers()->EnumerateHeader( 412 &iter, "Content-Disposition", &content_disposition)); 413 EXPECT_EQ(kTestContentDisposition, content_disposition); 414 } 415 416 } // namespace content 417