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.
5 #include "base/basictypes.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/macros.h"
11 #include "base/memory/ref_counted.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/numerics/safe_conversions.h"
14 #include "base/run_loop.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "base/time/time.h"
17 #include "content/browser/fileapi/mock_url_request_delegate.h"
18 #include "content/public/test/async_file_test_helper.h"
19 #include "content/public/test/test_file_system_context.h"
20 #include "net/base/net_errors.h"
21 #include "net/base/request_priority.h"
22 #include "net/base/test_completion_callback.h"
23 #include "net/disk_cache/disk_cache.h"
24 #include "net/http/http_byte_range.h"
25 #include "net/http/http_request_headers.h"
26 #include "net/http/http_response_headers.h"
27 #include "net/url_request/url_request.h"
28 #include "net/url_request/url_request_context.h"
29 #include "net/url_request/url_request_job_factory_impl.h"
30 #include "storage/browser/blob/blob_data_builder.h"
31 #include "storage/browser/blob/blob_data_handle.h"
32 #include "storage/browser/blob/blob_data_snapshot.h"
33 #include "storage/browser/blob/blob_storage_context.h"
34 #include "storage/browser/blob/blob_url_request_job.h"
35 #include "storage/browser/fileapi/file_system_context.h"
36 #include "storage/browser/fileapi/file_system_operation_context.h"
37 #include "storage/browser/fileapi/file_system_url.h"
38 #include "testing/gtest/include/gtest/gtest.h"
40 using storage::BlobDataSnapshot
;
41 using storage::BlobDataBuilder
;
42 using storage::BlobURLRequestJob
;
48 const int kBufferSize
= 1024;
49 const char kTestData1
[] = "Hello";
50 const char kTestData2
[] = "Here it is data.";
51 const char kTestFileData1
[] = "0123456789";
52 const char kTestFileData2
[] = "This is sample file.";
53 const char kTestFileSystemFileData1
[] = "abcdefghijklmnop";
54 const char kTestFileSystemFileData2
[] = "File system file test data.";
55 const char kTestDiskCacheKey
[] = "pipe";
56 const char kTestDiskCacheData
[] = "Ceci n'est pas un pamplemousse.";
57 const char kTestContentType
[] = "foo/bar";
58 const char kTestContentDisposition
[] = "attachment; filename=foo.txt";
60 const char kFileSystemURLOrigin
[] = "http://remote";
61 const storage::FileSystemType kFileSystemType
=
62 storage::kFileSystemTypeTemporary
;
64 const int kTestDiskCacheStreamIndex
= 0;
66 // Our disk cache tests don't need a real data handle since the tests themselves
67 // scope the disk cache and entries.
68 class EmptyDataHandle
: public storage::BlobDataBuilder::DataHandle
{
70 ~EmptyDataHandle() override
{}
73 scoped_ptr
<disk_cache::Backend
> CreateInMemoryDiskCache() {
74 scoped_ptr
<disk_cache::Backend
> cache
;
75 net::TestCompletionCallback callback
;
76 int rv
= disk_cache::CreateCacheBackend(net::MEMORY_CACHE
,
77 net::CACHE_BACKEND_DEFAULT
,
79 false, nullptr, nullptr, &cache
,
81 EXPECT_EQ(net::OK
, callback
.GetResult(rv
));
86 disk_cache::ScopedEntryPtr
CreateDiskCacheEntry(disk_cache::Backend
* cache
,
88 const std::string
& data
) {
89 disk_cache::Entry
* temp_entry
= nullptr;
90 net::TestCompletionCallback callback
;
91 int rv
= cache
->CreateEntry(key
, &temp_entry
, callback
.callback());
92 if (callback
.GetResult(rv
) != net::OK
)
94 disk_cache::ScopedEntryPtr
entry(temp_entry
);
96 scoped_refptr
<net::StringIOBuffer
> iobuffer
= new net::StringIOBuffer(data
);
97 rv
= entry
->WriteData(kTestDiskCacheStreamIndex
, 0, iobuffer
.get(),
98 iobuffer
->size(), callback
.callback(), false);
99 EXPECT_EQ(static_cast<int>(data
.size()), callback
.GetResult(rv
));
105 class BlobURLRequestJobTest
: public testing::Test
{
107 // A simple ProtocolHandler implementation to create BlobURLRequestJob.
108 class MockProtocolHandler
:
109 public net::URLRequestJobFactory::ProtocolHandler
{
111 MockProtocolHandler(BlobURLRequestJobTest
* test
) : test_(test
) {}
113 // net::URLRequestJobFactory::ProtocolHandler override.
114 net::URLRequestJob
* MaybeCreateJob(
115 net::URLRequest
* request
,
116 net::NetworkDelegate
* network_delegate
) const override
{
117 return new BlobURLRequestJob(request
, network_delegate
,
118 test_
->GetSnapshotFromBuilder(),
119 test_
->file_system_context_
.get(),
120 base::ThreadTaskRunnerHandle::Get().get());
124 BlobURLRequestJobTest
* test_
;
127 BlobURLRequestJobTest()
128 : blob_data_(new BlobDataBuilder("uuid")), expected_status_code_(0) {}
130 void SetUp() override
{
131 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
133 temp_file1_
= temp_dir_
.path().AppendASCII("BlobFile1.dat");
134 ASSERT_EQ(static_cast<int>(arraysize(kTestFileData1
) - 1),
135 base::WriteFile(temp_file1_
, kTestFileData1
,
136 arraysize(kTestFileData1
) - 1));
137 base::File::Info file_info1
;
138 base::GetFileInfo(temp_file1_
, &file_info1
);
139 temp_file_modification_time1_
= file_info1
.last_modified
;
141 temp_file2_
= temp_dir_
.path().AppendASCII("BlobFile2.dat");
142 ASSERT_EQ(static_cast<int>(arraysize(kTestFileData2
) - 1),
143 base::WriteFile(temp_file2_
, kTestFileData2
,
144 arraysize(kTestFileData2
) - 1));
145 base::File::Info file_info2
;
146 base::GetFileInfo(temp_file2_
, &file_info2
);
147 temp_file_modification_time2_
= file_info2
.last_modified
;
149 disk_cache_backend_
= CreateInMemoryDiskCache();
150 disk_cache_entry_
= CreateDiskCacheEntry(
151 disk_cache_backend_
.get(), kTestDiskCacheKey
, kTestDiskCacheData
);
153 url_request_job_factory_
.SetProtocolHandler("blob",
154 new MockProtocolHandler(this));
155 url_request_context_
.set_job_factory(&url_request_job_factory_
);
158 void TearDown() override
{
159 blob_handle_
.reset();
161 base::RunLoop run_loop
;
162 run_loop
.RunUntilIdle();
165 void SetUpFileSystem() {
166 // Prepare file system.
167 file_system_context_
= CreateFileSystemContextForTesting(
168 NULL
, temp_dir_
.path());
170 file_system_context_
->OpenFileSystem(
171 GURL(kFileSystemURLOrigin
),
173 storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT
,
174 base::Bind(&BlobURLRequestJobTest::OnValidateFileSystem
,
175 base::Unretained(this)));
176 base::RunLoop().RunUntilIdle();
177 ASSERT_TRUE(file_system_root_url_
.is_valid());
179 // Prepare files on file system.
180 const char kFilename1
[] = "FileSystemFile1.dat";
181 temp_file_system_file1_
= GetFileSystemURL(kFilename1
);
182 WriteFileSystemFile(kFilename1
, kTestFileSystemFileData1
,
183 arraysize(kTestFileSystemFileData1
) - 1,
184 &temp_file_system_file_modification_time1_
);
185 const char kFilename2
[] = "FileSystemFile2.dat";
186 temp_file_system_file2_
= GetFileSystemURL(kFilename2
);
187 WriteFileSystemFile(kFilename2
, kTestFileSystemFileData2
,
188 arraysize(kTestFileSystemFileData2
) - 1,
189 &temp_file_system_file_modification_time2_
);
192 GURL
GetFileSystemURL(const std::string
& filename
) {
193 return GURL(file_system_root_url_
.spec() + filename
);
196 void WriteFileSystemFile(const std::string
& filename
,
197 const char* buf
, int buf_size
,
198 base::Time
* modification_time
) {
199 storage::FileSystemURL url
=
200 file_system_context_
->CreateCrackedFileSystemURL(
201 GURL(kFileSystemURLOrigin
),
203 base::FilePath().AppendASCII(filename
));
205 ASSERT_EQ(base::File::FILE_OK
,
206 content::AsyncFileTestHelper::CreateFileWithData(
207 file_system_context_
.get(), url
, buf
, buf_size
));
209 base::File::Info file_info
;
210 ASSERT_EQ(base::File::FILE_OK
,
211 content::AsyncFileTestHelper::GetMetadata(
212 file_system_context_
.get(), url
, &file_info
));
213 if (modification_time
)
214 *modification_time
= file_info
.last_modified
;
217 void OnValidateFileSystem(const GURL
& root
,
218 const std::string
& name
,
219 base::File::Error result
) {
220 ASSERT_EQ(base::File::FILE_OK
, result
);
221 ASSERT_TRUE(root
.is_valid());
222 file_system_root_url_
= root
;
225 void TestSuccessNonrangeRequest(const std::string
& expected_response
,
226 int64 expected_content_length
) {
227 expected_status_code_
= 200;
228 expected_response_
= expected_response
;
229 TestRequest("GET", net::HttpRequestHeaders());
230 EXPECT_EQ(expected_content_length
,
231 request_
->response_headers()->GetContentLength());
234 void TestErrorRequest(int expected_status_code
) {
235 expected_status_code_
= expected_status_code
;
236 expected_response_
= "";
237 TestRequest("GET", net::HttpRequestHeaders());
240 void TestRequest(const std::string
& method
,
241 const net::HttpRequestHeaders
& extra_headers
) {
242 request_
= url_request_context_
.CreateRequest(
243 GURL("blob:blah"), net::DEFAULT_PRIORITY
, &url_request_delegate_
);
244 request_
->set_method(method
);
245 if (!extra_headers
.IsEmpty())
246 request_
->SetExtraRequestHeaders(extra_headers
);
249 base::MessageLoop::current()->Run();
252 EXPECT_TRUE(request_
->status().is_success());
253 EXPECT_EQ(expected_status_code_
,
254 request_
->response_headers()->response_code());
255 EXPECT_EQ(expected_response_
, url_request_delegate_
.response_data());
258 void BuildComplicatedData(std::string
* expected_result
) {
259 blob_data_
->AppendData(kTestData1
+ 1, 2);
260 *expected_result
= std::string(kTestData1
+ 1, 2);
262 blob_data_
->AppendFile(temp_file1_
, 2, 3, temp_file_modification_time1_
);
263 *expected_result
+= std::string(kTestFileData1
+ 2, 3);
265 blob_data_
->AppendDiskCacheEntry(new EmptyDataHandle(),
266 disk_cache_entry_
.get(),
267 kTestDiskCacheStreamIndex
);
268 *expected_result
+= std::string(kTestDiskCacheData
);
270 blob_data_
->AppendFileSystemFile(temp_file_system_file1_
, 3, 4,
271 temp_file_system_file_modification_time1_
);
272 *expected_result
+= std::string(kTestFileSystemFileData1
+ 3, 4);
274 blob_data_
->AppendData(kTestData2
+ 4, 5);
275 *expected_result
+= std::string(kTestData2
+ 4, 5);
277 blob_data_
->AppendFile(temp_file2_
, 5, 6, temp_file_modification_time2_
);
278 *expected_result
+= std::string(kTestFileData2
+ 5, 6);
280 blob_data_
->AppendFileSystemFile(temp_file_system_file2_
, 6, 7,
281 temp_file_system_file_modification_time2_
);
282 *expected_result
+= std::string(kTestFileSystemFileData2
+ 6, 7);
285 scoped_ptr
<BlobDataSnapshot
> GetSnapshotFromBuilder() {
287 blob_handle_
= blob_context_
.AddFinishedBlob(blob_data_
.get()).Pass();
289 return blob_handle_
->CreateSnapshot().Pass();
292 // This only works if all the Blob items have a definite pre-computed length.
293 // Otherwise, this will fail a CHECK.
294 int64
GetTotalBlobLength() {
296 scoped_ptr
<BlobDataSnapshot
> data
= GetSnapshotFromBuilder();
297 const auto& items
= data
->items();
298 for (const auto& item
: items
) {
299 int64 length
= base::checked_cast
<int64
>(item
->length());
300 CHECK(length
<= kint64max
- total
);
307 base::ScopedTempDir temp_dir_
;
308 base::FilePath temp_file1_
;
309 base::FilePath temp_file2_
;
310 base::Time temp_file_modification_time1_
;
311 base::Time temp_file_modification_time2_
;
312 GURL file_system_root_url_
;
313 GURL temp_file_system_file1_
;
314 GURL temp_file_system_file2_
;
315 base::Time temp_file_system_file_modification_time1_
;
316 base::Time temp_file_system_file_modification_time2_
;
318 scoped_ptr
<disk_cache::Backend
> disk_cache_backend_
;
319 disk_cache::ScopedEntryPtr disk_cache_entry_
;
321 base::MessageLoopForIO message_loop_
;
322 scoped_refptr
<storage::FileSystemContext
> file_system_context_
;
324 storage::BlobStorageContext blob_context_
;
325 scoped_ptr
<storage::BlobDataHandle
> blob_handle_
;
326 scoped_ptr
<BlobDataBuilder
> blob_data_
;
327 scoped_ptr
<BlobDataSnapshot
> blob_data_snapshot_
;
328 net::URLRequestJobFactoryImpl url_request_job_factory_
;
329 net::URLRequestContext url_request_context_
;
330 MockURLRequestDelegate url_request_delegate_
;
331 scoped_ptr
<net::URLRequest
> request_
;
333 int expected_status_code_
;
334 std::string expected_response_
;
337 TEST_F(BlobURLRequestJobTest
, TestGetSimpleDataRequest
) {
338 blob_data_
->AppendData(kTestData1
);
339 TestSuccessNonrangeRequest(kTestData1
, arraysize(kTestData1
) - 1);
342 TEST_F(BlobURLRequestJobTest
, TestGetSimpleFileRequest
) {
343 blob_data_
->AppendFile(temp_file1_
, 0, kuint64max
, base::Time());
344 TestSuccessNonrangeRequest(kTestFileData1
, arraysize(kTestFileData1
) - 1);
347 TEST_F(BlobURLRequestJobTest
, TestGetLargeFileRequest
) {
348 base::FilePath large_temp_file
=
349 temp_dir_
.path().AppendASCII("LargeBlob.dat");
350 std::string large_data
;
351 large_data
.reserve(kBufferSize
* 5);
352 for (int i
= 0; i
< kBufferSize
* 5; ++i
)
353 large_data
.append(1, static_cast<char>(i
% 256));
354 ASSERT_EQ(static_cast<int>(large_data
.size()),
355 base::WriteFile(large_temp_file
, large_data
.data(),
357 blob_data_
->AppendFile(large_temp_file
, 0, kuint64max
, base::Time());
358 TestSuccessNonrangeRequest(large_data
, large_data
.size());
361 TEST_F(BlobURLRequestJobTest
, TestGetNonExistentFileRequest
) {
362 base::FilePath non_existent_file
=
363 temp_file1_
.InsertBeforeExtension(FILE_PATH_LITERAL("-na"));
364 blob_data_
->AppendFile(non_existent_file
, 0, kuint64max
, base::Time());
365 TestErrorRequest(404);
368 TEST_F(BlobURLRequestJobTest
, TestGetChangedFileRequest
) {
369 base::Time old_time
=
370 temp_file_modification_time1_
- base::TimeDelta::FromSeconds(10);
371 blob_data_
->AppendFile(temp_file1_
, 0, 3, old_time
);
372 TestErrorRequest(404);
375 TEST_F(BlobURLRequestJobTest
, TestGetSlicedFileRequest
) {
376 blob_data_
->AppendFile(temp_file1_
, 2, 4, temp_file_modification_time1_
);
377 std::string
result(kTestFileData1
+ 2, 4);
378 TestSuccessNonrangeRequest(result
, 4);
381 TEST_F(BlobURLRequestJobTest
, TestGetSimpleFileSystemFileRequest
) {
383 blob_data_
->AppendFileSystemFile(temp_file_system_file1_
, 0, kuint64max
,
385 TestSuccessNonrangeRequest(kTestFileSystemFileData1
,
386 arraysize(kTestFileSystemFileData1
) - 1);
389 TEST_F(BlobURLRequestJobTest
, TestGetLargeFileSystemFileRequest
) {
391 std::string large_data
;
392 large_data
.reserve(kBufferSize
* 5);
393 for (int i
= 0; i
< kBufferSize
* 5; ++i
)
394 large_data
.append(1, static_cast<char>(i
% 256));
396 const char kFilename
[] = "LargeBlob.dat";
397 WriteFileSystemFile(kFilename
, large_data
.data(), large_data
.size(), NULL
);
399 blob_data_
->AppendFileSystemFile(GetFileSystemURL(kFilename
), 0, kuint64max
,
401 TestSuccessNonrangeRequest(large_data
, large_data
.size());
404 TEST_F(BlobURLRequestJobTest
, TestGetNonExistentFileSystemFileRequest
) {
406 GURL non_existent_file
= GetFileSystemURL("non-existent.dat");
407 blob_data_
->AppendFileSystemFile(non_existent_file
, 0, kuint64max
,
409 TestErrorRequest(404);
412 TEST_F(BlobURLRequestJobTest
, TestGetInvalidFileSystemFileRequest
) {
415 blob_data_
->AppendFileSystemFile(invalid_file
, 0, kuint64max
, base::Time());
416 TestErrorRequest(500);
419 TEST_F(BlobURLRequestJobTest
, TestGetChangedFileSystemFileRequest
) {
421 base::Time old_time
=
422 temp_file_system_file_modification_time1_
-
423 base::TimeDelta::FromSeconds(10);
424 blob_data_
->AppendFileSystemFile(temp_file_system_file1_
, 0, 3, old_time
);
425 TestErrorRequest(404);
428 TEST_F(BlobURLRequestJobTest
, TestGetSlicedFileSystemFileRequest
) {
430 blob_data_
->AppendFileSystemFile(temp_file_system_file1_
, 2, 4,
431 temp_file_system_file_modification_time1_
);
432 std::string
result(kTestFileSystemFileData1
+ 2, 4);
433 TestSuccessNonrangeRequest(result
, 4);
436 TEST_F(BlobURLRequestJobTest
, TestGetSimpleDiskCacheRequest
) {
437 blob_data_
->AppendDiskCacheEntry(new EmptyDataHandle(),
438 disk_cache_entry_
.get(),
439 kTestDiskCacheStreamIndex
);
440 TestSuccessNonrangeRequest(kTestDiskCacheData
,
441 arraysize(kTestDiskCacheData
) - 1);
444 TEST_F(BlobURLRequestJobTest
, TestGetComplicatedDataFileAndDiskCacheRequest
) {
447 BuildComplicatedData(&result
);
448 TestSuccessNonrangeRequest(result
, GetTotalBlobLength());
451 TEST_F(BlobURLRequestJobTest
, TestGetRangeRequest1
) {
454 BuildComplicatedData(&result
);
455 net::HttpRequestHeaders extra_headers
;
456 extra_headers
.SetHeader(net::HttpRequestHeaders::kRange
,
457 net::HttpByteRange::Bounded(5, 10).GetHeaderValue());
458 expected_status_code_
= 206;
459 expected_response_
= result
.substr(5, 10 - 5 + 1);
460 TestRequest("GET", extra_headers
);
462 EXPECT_EQ(6, request_
->response_headers()->GetContentLength());
464 int64 first
= 0, last
= 0, length
= 0;
466 request_
->response_headers()->GetContentRange(&first
, &last
, &length
));
469 EXPECT_EQ(GetTotalBlobLength(), length
);
472 TEST_F(BlobURLRequestJobTest
, TestGetRangeRequest2
) {
475 BuildComplicatedData(&result
);
476 net::HttpRequestHeaders extra_headers
;
477 extra_headers
.SetHeader(net::HttpRequestHeaders::kRange
,
478 net::HttpByteRange::Suffix(10).GetHeaderValue());
479 expected_status_code_
= 206;
480 expected_response_
= result
.substr(result
.length() - 10);
481 TestRequest("GET", extra_headers
);
483 EXPECT_EQ(10, request_
->response_headers()->GetContentLength());
485 int64 total
= GetTotalBlobLength();
486 int64 first
= 0, last
= 0, length
= 0;
488 request_
->response_headers()->GetContentRange(&first
, &last
, &length
));
489 EXPECT_EQ(total
- 10, first
);
490 EXPECT_EQ(total
- 1, last
);
491 EXPECT_EQ(total
, length
);
494 TEST_F(BlobURLRequestJobTest
, TestExtraHeaders
) {
495 blob_data_
->set_content_type(kTestContentType
);
496 blob_data_
->set_content_disposition(kTestContentDisposition
);
497 blob_data_
->AppendData(kTestData1
);
498 expected_status_code_
= 200;
499 expected_response_
= kTestData1
;
500 TestRequest("GET", net::HttpRequestHeaders());
502 std::string content_type
;
503 EXPECT_TRUE(request_
->response_headers()->GetMimeType(&content_type
));
504 EXPECT_EQ(kTestContentType
, content_type
);
506 std::string content_disposition
;
507 EXPECT_TRUE(request_
->response_headers()->EnumerateHeader(
508 &iter
, "Content-Disposition", &content_disposition
));
509 EXPECT_EQ(kTestContentDisposition
, content_disposition
);
512 } // namespace content