1 // Copyright (c) 2012 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 "content/browser/download/base_file.h"
7 #include "base/file_util.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/test/test_file_util.h"
13 #include "content/browser/browser_thread_impl.h"
14 #include "content/public/browser/download_interrupt_reasons.h"
15 #include "crypto/secure_hash.h"
16 #include "crypto/sha2.h"
17 #include "net/base/file_stream.h"
18 #include "net/base/mock_file_stream.h"
19 #include "testing/gtest/include/gtest/gtest.h"
24 const char kTestData1
[] = "Let's write some data to the file!\n";
25 const char kTestData2
[] = "Writing more data.\n";
26 const char kTestData3
[] = "Final line.";
27 const char kTestData4
[] = "supercalifragilisticexpialidocious";
28 const int kTestDataLength1
= arraysize(kTestData1
) - 1;
29 const int kTestDataLength2
= arraysize(kTestData2
) - 1;
30 const int kTestDataLength3
= arraysize(kTestData3
) - 1;
31 const int kTestDataLength4
= arraysize(kTestData4
) - 1;
32 const int kElapsedTimeSeconds
= 5;
33 const base::TimeDelta kElapsedTimeDelta
= base::TimeDelta::FromSeconds(
38 class BaseFileTest
: public testing::Test
{
40 static const unsigned char kEmptySha256Hash
[crypto::kSHA256Length
];
43 : expect_file_survives_(false),
44 expect_in_progress_(true),
45 expected_error_(DOWNLOAD_INTERRUPT_REASON_NONE
),
46 file_thread_(BrowserThread::FILE, &message_loop_
) {
49 virtual void SetUp() {
51 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
52 base_file_
.reset(new BaseFile(base::FilePath(),
58 scoped_ptr
<net::FileStream
>(),
62 virtual void TearDown() {
63 EXPECT_FALSE(base_file_
->in_progress());
64 if (!expected_error_
) {
65 EXPECT_EQ(static_cast<int64
>(expected_data_
.size()),
66 base_file_
->bytes_so_far());
69 base::FilePath full_path
= base_file_
->full_path();
71 if (!expected_data_
.empty() && !expected_error_
) {
72 // Make sure the data has been properly written to disk.
73 std::string disk_data
;
74 EXPECT_TRUE(base::ReadFileToString(full_path
, &disk_data
));
75 EXPECT_EQ(expected_data_
, disk_data
);
78 // Make sure the mock BrowserThread outlives the BaseFile to satisfy
79 // thread checks inside it.
82 EXPECT_EQ(expect_file_survives_
, base::PathExists(full_path
));
86 secure_hash_
.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256
));
87 memcpy(sha256_hash_
, kEmptySha256Hash
, crypto::kSHA256Length
);
90 void UpdateHash(const char* data
, size_t length
) {
91 secure_hash_
->Update(data
, length
);
94 std::string
GetFinalHash() {
96 secure_hash_
->Finish(sha256_hash_
, crypto::kSHA256Length
);
97 hash
.assign(reinterpret_cast<const char*>(sha256_hash_
),
98 sizeof(sha256_hash_
));
102 void MakeFileWithHash() {
103 base_file_
.reset(new BaseFile(base::FilePath(),
109 scoped_ptr
<net::FileStream
>(),
110 net::BoundNetLog()));
113 bool InitializeFile() {
114 DownloadInterruptReason result
= base_file_
->Initialize(temp_dir_
.path());
115 EXPECT_EQ(expected_error_
, result
);
116 return result
== DOWNLOAD_INTERRUPT_REASON_NONE
;
119 bool AppendDataToFile(const std::string
& data
) {
120 EXPECT_EQ(expect_in_progress_
, base_file_
->in_progress());
121 DownloadInterruptReason result
=
122 base_file_
->AppendDataToFile(data
.data(), data
.size());
123 if (result
== DOWNLOAD_INTERRUPT_REASON_NONE
)
124 EXPECT_TRUE(expect_in_progress_
) << " result = " << result
;
126 EXPECT_EQ(expected_error_
, result
);
127 if (base_file_
->in_progress()) {
128 expected_data_
+= data
;
129 if (expected_error_
== DOWNLOAD_INTERRUPT_REASON_NONE
) {
130 EXPECT_EQ(static_cast<int64
>(expected_data_
.size()),
131 base_file_
->bytes_so_far());
134 return result
== DOWNLOAD_INTERRUPT_REASON_NONE
;
137 void set_expected_data(const std::string
& data
) { expected_data_
= data
; }
140 // Create a file. Returns the complete file path.
141 base::FilePath
CreateTestFile() {
142 base::FilePath file_name
;
143 BaseFile
file(base::FilePath(),
149 scoped_ptr
<net::FileStream
>(),
152 EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE
,
153 file
.Initialize(temp_dir_
.path()));
154 file_name
= file
.full_path();
155 EXPECT_NE(base::FilePath::StringType(), file_name
.value());
157 EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE
,
158 file
.AppendDataToFile(kTestData4
, kTestDataLength4
));
160 // Keep the file from getting deleted when existing_file_name is deleted.
166 // Create a file with the specified file name.
167 void CreateFileWithName(const base::FilePath
& file_name
) {
168 EXPECT_NE(base::FilePath::StringType(), file_name
.value());
169 BaseFile
duplicate_file(file_name
,
175 scoped_ptr
<net::FileStream
>(),
177 EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE
,
178 duplicate_file
.Initialize(temp_dir_
.path()));
179 // Write something into it.
180 duplicate_file
.AppendDataToFile(kTestData4
, kTestDataLength4
);
181 // Detach the file so it isn't deleted on destruction of |duplicate_file|.
182 duplicate_file
.Detach();
185 int64
CurrentSpeedAtTime(base::TimeTicks current_time
) {
186 EXPECT_TRUE(base_file_
.get());
187 return base_file_
->CurrentSpeedAtTime(current_time
);
190 base::TimeTicks
StartTick() {
191 EXPECT_TRUE(base_file_
.get());
192 return base_file_
->start_tick_
;
195 void set_expected_error(DownloadInterruptReason err
) {
196 expected_error_
= err
;
200 linked_ptr
<net::testing::MockFileStream
> mock_file_stream_
;
202 // BaseClass instance we are testing.
203 scoped_ptr
<BaseFile
> base_file_
;
205 // Temporary directory for renamed downloads.
206 base::ScopedTempDir temp_dir_
;
208 // Expect the file to survive deletion of the BaseFile instance.
209 bool expect_file_survives_
;
211 // Expect the file to be in progress.
212 bool expect_in_progress_
;
215 scoped_ptr
<crypto::SecureHash
> secure_hash_
;
217 unsigned char sha256_hash_
[crypto::kSHA256Length
];
220 // Keep track of what data should be saved to the disk file.
221 std::string expected_data_
;
222 DownloadInterruptReason expected_error_
;
224 // Mock file thread to satisfy debug checks in BaseFile.
225 base::MessageLoop message_loop_
;
226 BrowserThreadImpl file_thread_
;
229 // This will initialize the entire array to zero.
230 const unsigned char BaseFileTest::kEmptySha256Hash
[] = { 0 };
232 // Test the most basic scenario: just create the object and do a sanity check
233 // on all its accessors. This is actually a case that rarely happens
234 // in production, where we would at least Initialize it.
235 TEST_F(BaseFileTest
, CreateDestroy
) {
236 EXPECT_EQ(base::FilePath().value(), base_file_
->full_path().value());
239 // Cancel the download explicitly.
240 TEST_F(BaseFileTest
, Cancel
) {
241 ASSERT_TRUE(InitializeFile());
242 EXPECT_TRUE(base::PathExists(base_file_
->full_path()));
243 base_file_
->Cancel();
244 EXPECT_FALSE(base::PathExists(base_file_
->full_path()));
245 EXPECT_NE(base::FilePath().value(), base_file_
->full_path().value());
248 // Write data to the file and detach it, so it doesn't get deleted
249 // automatically when base_file_ is destructed.
250 TEST_F(BaseFileTest
, WriteAndDetach
) {
251 ASSERT_TRUE(InitializeFile());
252 ASSERT_TRUE(AppendDataToFile(kTestData1
));
253 base_file_
->Finish();
254 base_file_
->Detach();
255 expect_file_survives_
= true;
258 // Write data to the file and detach it, and calculate its sha256 hash.
259 TEST_F(BaseFileTest
, WriteWithHashAndDetach
) {
260 // Calculate the final hash.
262 UpdateHash(kTestData1
, kTestDataLength1
);
263 std::string expected_hash
= GetFinalHash();
264 std::string expected_hash_hex
=
265 base::HexEncode(expected_hash
.data(), expected_hash
.size());
268 ASSERT_TRUE(InitializeFile());
269 ASSERT_TRUE(AppendDataToFile(kTestData1
));
270 base_file_
->Finish();
273 base_file_
->GetHash(&hash
);
274 EXPECT_EQ("0B2D3F3F7943AD64B860DF94D05CB56A8A97C6EC5768B5B70B930C5AA7FA9ADE",
276 EXPECT_EQ(expected_hash_hex
, base::HexEncode(hash
.data(), hash
.size()));
278 base_file_
->Detach();
279 expect_file_survives_
= true;
282 // Rename the file after writing to it, then detach.
283 TEST_F(BaseFileTest
, WriteThenRenameAndDetach
) {
284 ASSERT_TRUE(InitializeFile());
286 base::FilePath
initial_path(base_file_
->full_path());
287 EXPECT_TRUE(base::PathExists(initial_path
));
288 base::FilePath
new_path(temp_dir_
.path().AppendASCII("NewFile"));
289 EXPECT_FALSE(base::PathExists(new_path
));
291 ASSERT_TRUE(AppendDataToFile(kTestData1
));
293 EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE
, base_file_
->Rename(new_path
));
294 EXPECT_FALSE(base::PathExists(initial_path
));
295 EXPECT_TRUE(base::PathExists(new_path
));
297 base_file_
->Finish();
298 base_file_
->Detach();
299 expect_file_survives_
= true;
302 // Write data to the file once.
303 TEST_F(BaseFileTest
, SingleWrite
) {
304 ASSERT_TRUE(InitializeFile());
305 ASSERT_TRUE(AppendDataToFile(kTestData1
));
306 base_file_
->Finish();
309 // Write data to the file multiple times.
310 TEST_F(BaseFileTest
, MultipleWrites
) {
311 ASSERT_TRUE(InitializeFile());
312 ASSERT_TRUE(AppendDataToFile(kTestData1
));
313 ASSERT_TRUE(AppendDataToFile(kTestData2
));
314 ASSERT_TRUE(AppendDataToFile(kTestData3
));
316 EXPECT_FALSE(base_file_
->GetHash(&hash
));
317 base_file_
->Finish();
320 // Write data to the file once and calculate its sha256 hash.
321 TEST_F(BaseFileTest
, SingleWriteWithHash
) {
322 // Calculate the final hash.
324 UpdateHash(kTestData1
, kTestDataLength1
);
325 std::string expected_hash
= GetFinalHash();
326 std::string expected_hash_hex
=
327 base::HexEncode(expected_hash
.data(), expected_hash
.size());
330 ASSERT_TRUE(InitializeFile());
331 // Can get partial hash states before Finish() is called.
332 EXPECT_STRNE(std::string().c_str(), base_file_
->GetHashState().c_str());
333 ASSERT_TRUE(AppendDataToFile(kTestData1
));
334 EXPECT_STRNE(std::string().c_str(), base_file_
->GetHashState().c_str());
335 base_file_
->Finish();
338 base_file_
->GetHash(&hash
);
339 EXPECT_EQ(expected_hash_hex
, base::HexEncode(hash
.data(), hash
.size()));
342 // Write data to the file multiple times and calculate its sha256 hash.
343 TEST_F(BaseFileTest
, MultipleWritesWithHash
) {
344 // Calculate the final hash.
346 UpdateHash(kTestData1
, kTestDataLength1
);
347 UpdateHash(kTestData2
, kTestDataLength2
);
348 UpdateHash(kTestData3
, kTestDataLength3
);
349 std::string expected_hash
= GetFinalHash();
350 std::string expected_hash_hex
=
351 base::HexEncode(expected_hash
.data(), expected_hash
.size());
355 ASSERT_TRUE(InitializeFile());
356 ASSERT_TRUE(AppendDataToFile(kTestData1
));
357 ASSERT_TRUE(AppendDataToFile(kTestData2
));
358 ASSERT_TRUE(AppendDataToFile(kTestData3
));
359 // No hash before Finish() is called.
360 EXPECT_FALSE(base_file_
->GetHash(&hash
));
361 base_file_
->Finish();
363 EXPECT_TRUE(base_file_
->GetHash(&hash
));
364 EXPECT_EQ("CBF68BF10F8003DB86B31343AFAC8C7175BD03FB5FC905650F8C80AF087443A8",
366 EXPECT_EQ(expected_hash_hex
, base::HexEncode(hash
.data(), hash
.size()));
369 // Write data to the file multiple times, interrupt it, and continue using
370 // another file. Calculate the resulting combined sha256 hash.
371 TEST_F(BaseFileTest
, MultipleWritesInterruptedWithHash
) {
372 // Calculate the final hash.
374 UpdateHash(kTestData1
, kTestDataLength1
);
375 UpdateHash(kTestData2
, kTestDataLength2
);
376 UpdateHash(kTestData3
, kTestDataLength3
);
377 std::string expected_hash
= GetFinalHash();
378 std::string expected_hash_hex
=
379 base::HexEncode(expected_hash
.data(), expected_hash
.size());
382 ASSERT_TRUE(InitializeFile());
384 ASSERT_TRUE(AppendDataToFile(kTestData1
));
385 ASSERT_TRUE(AppendDataToFile(kTestData2
));
386 // Get the hash state and file name.
387 std::string hash_state
;
388 hash_state
= base_file_
->GetHashState();
390 base_file_
->Finish();
392 base::FilePath
new_file_path(temp_dir_
.path().Append(
393 base::FilePath(FILE_PATH_LITERAL("second_file"))));
395 ASSERT_TRUE(base::CopyFile(base_file_
->full_path(), new_file_path
));
397 // Create another file
398 BaseFile
second_file(new_file_path
,
401 base_file_
->bytes_so_far(),
404 scoped_ptr
<net::FileStream
>(),
406 ASSERT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE
,
407 second_file
.Initialize(base::FilePath()));
408 std::string
data(kTestData3
);
409 EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE
,
410 second_file
.AppendDataToFile(data
.data(), data
.size()));
411 second_file
.Finish();
414 EXPECT_TRUE(second_file
.GetHash(&hash
));
415 // This will fail until getting the hash state is supported in SecureHash.
416 EXPECT_STREQ(expected_hash_hex
.c_str(),
417 base::HexEncode(hash
.data(), hash
.size()).c_str());
420 // Rename the file after all writes to it.
421 TEST_F(BaseFileTest
, WriteThenRename
) {
422 ASSERT_TRUE(InitializeFile());
424 base::FilePath
initial_path(base_file_
->full_path());
425 EXPECT_TRUE(base::PathExists(initial_path
));
426 base::FilePath
new_path(temp_dir_
.path().AppendASCII("NewFile"));
427 EXPECT_FALSE(base::PathExists(new_path
));
429 ASSERT_TRUE(AppendDataToFile(kTestData1
));
431 EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE
,
432 base_file_
->Rename(new_path
));
433 EXPECT_FALSE(base::PathExists(initial_path
));
434 EXPECT_TRUE(base::PathExists(new_path
));
436 base_file_
->Finish();
439 // Rename the file while the download is still in progress.
440 TEST_F(BaseFileTest
, RenameWhileInProgress
) {
441 ASSERT_TRUE(InitializeFile());
443 base::FilePath
initial_path(base_file_
->full_path());
444 EXPECT_TRUE(base::PathExists(initial_path
));
445 base::FilePath
new_path(temp_dir_
.path().AppendASCII("NewFile"));
446 EXPECT_FALSE(base::PathExists(new_path
));
448 ASSERT_TRUE(AppendDataToFile(kTestData1
));
450 EXPECT_TRUE(base_file_
->in_progress());
451 EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE
, base_file_
->Rename(new_path
));
452 EXPECT_FALSE(base::PathExists(initial_path
));
453 EXPECT_TRUE(base::PathExists(new_path
));
455 ASSERT_TRUE(AppendDataToFile(kTestData2
));
457 base_file_
->Finish();
460 // Test that a failed rename reports the correct error.
461 TEST_F(BaseFileTest
, RenameWithError
) {
462 ASSERT_TRUE(InitializeFile());
464 // TestDir is a subdirectory in |temp_dir_| that we will make read-only so
465 // that the rename will fail.
466 base::FilePath
test_dir(temp_dir_
.path().AppendASCII("TestDir"));
467 ASSERT_TRUE(base::CreateDirectory(test_dir
));
469 base::FilePath
new_path(test_dir
.AppendASCII("TestFile"));
470 EXPECT_FALSE(base::PathExists(new_path
));
473 file_util::PermissionRestorer
restore_permissions_for(test_dir
);
474 ASSERT_TRUE(file_util::MakeFileUnwritable(test_dir
));
475 EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED
,
476 base_file_
->Rename(new_path
));
479 base_file_
->Finish();
482 // Write data to the file multiple times.
483 TEST_F(BaseFileTest
, MultipleWritesWithError
) {
485 ASSERT_TRUE(base::CreateTemporaryFile(&path
));
486 // Create a new file stream. scoped_ptr takes ownership and passes it to
487 // BaseFile; we use the pointer anyway and rely on the BaseFile not
488 // deleting the MockFileStream until the BaseFile is reset.
489 net::testing::MockFileStream
* mock_file_stream(
490 new net::testing::MockFileStream(NULL
));
491 scoped_ptr
<net::FileStream
> mock_file_stream_scoped_ptr(mock_file_stream
);
494 mock_file_stream
->OpenSync(
496 base::PLATFORM_FILE_OPEN_ALWAYS
| base::PLATFORM_FILE_WRITE
));
498 // Copy of mock_file_stream; we pass ownership and rely on the BaseFile
499 // not deleting it until it is reset.
501 base_file_
.reset(new BaseFile(mock_file_stream
->get_path(),
507 mock_file_stream_scoped_ptr
.Pass(),
508 net::BoundNetLog()));
509 ASSERT_TRUE(InitializeFile());
510 ASSERT_TRUE(AppendDataToFile(kTestData1
));
511 ASSERT_TRUE(AppendDataToFile(kTestData2
));
512 mock_file_stream
->set_forced_error(net::ERR_ACCESS_DENIED
);
513 set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED
);
514 ASSERT_FALSE(AppendDataToFile(kTestData3
));
516 EXPECT_FALSE(base_file_
->GetHash(&hash
));
517 base_file_
->Finish();
520 // Try to write to uninitialized file.
521 TEST_F(BaseFileTest
, UninitializedFile
) {
522 expect_in_progress_
= false;
523 set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED
);
524 EXPECT_FALSE(AppendDataToFile(kTestData1
));
527 // Create two |BaseFile|s with the same file, and attempt to write to both.
528 // Overwrite base_file_ with another file with the same name and
529 // non-zero contents, and make sure the last file to close 'wins'.
530 TEST_F(BaseFileTest
, DuplicateBaseFile
) {
531 ASSERT_TRUE(InitializeFile());
533 // Create another |BaseFile| referring to the file that |base_file_| owns.
534 CreateFileWithName(base_file_
->full_path());
536 ASSERT_TRUE(AppendDataToFile(kTestData1
));
537 base_file_
->Finish();
540 // Create a file and append to it.
541 TEST_F(BaseFileTest
, AppendToBaseFile
) {
542 // Create a new file.
543 base::FilePath existing_file_name
= CreateTestFile();
545 set_expected_data(kTestData4
);
547 // Use the file we've just created.
548 base_file_
.reset(new BaseFile(existing_file_name
,
554 scoped_ptr
<net::FileStream
>(),
555 net::BoundNetLog()));
557 ASSERT_TRUE(InitializeFile());
559 const base::FilePath file_name
= base_file_
->full_path();
560 EXPECT_NE(base::FilePath::StringType(), file_name
.value());
562 // Write into the file.
563 EXPECT_TRUE(AppendDataToFile(kTestData1
));
565 base_file_
->Finish();
566 base_file_
->Detach();
567 expect_file_survives_
= true;
570 // Create a read-only file and attempt to write to it.
571 TEST_F(BaseFileTest
, ReadonlyBaseFile
) {
572 // Create a new file.
573 base::FilePath readonly_file_name
= CreateTestFile();
575 // Restore permissions to the file when we are done with this test.
576 file_util::PermissionRestorer
restore_permissions(readonly_file_name
);
578 // Make it read-only.
579 EXPECT_TRUE(file_util::MakeFileUnwritable(readonly_file_name
));
581 // Try to overwrite it.
582 base_file_
.reset(new BaseFile(readonly_file_name
,
588 scoped_ptr
<net::FileStream
>(),
589 net::BoundNetLog()));
591 expect_in_progress_
= false;
592 set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED
);
593 EXPECT_FALSE(InitializeFile());
595 const base::FilePath file_name
= base_file_
->full_path();
596 EXPECT_NE(base::FilePath::StringType(), file_name
.value());
598 // Write into the file.
599 set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED
);
600 EXPECT_FALSE(AppendDataToFile(kTestData1
));
602 base_file_
->Finish();
603 base_file_
->Detach();
604 expect_file_survives_
= true;
607 TEST_F(BaseFileTest
, IsEmptyHash
) {
608 std::string
empty(crypto::kSHA256Length
, '\x00');
609 EXPECT_TRUE(BaseFile::IsEmptyHash(empty
));
610 std::string
not_empty(crypto::kSHA256Length
, '\x01');
611 EXPECT_FALSE(BaseFile::IsEmptyHash(not_empty
));
612 EXPECT_FALSE(BaseFile::IsEmptyHash(std::string()));
614 std::string also_not_empty
= empty
;
615 also_not_empty
[crypto::kSHA256Length
- 1] = '\x01';
616 EXPECT_FALSE(BaseFile::IsEmptyHash(also_not_empty
));
619 // Test that a temporary file is created in the default download directory.
620 TEST_F(BaseFileTest
, CreatedInDefaultDirectory
) {
621 ASSERT_TRUE(base_file_
->full_path().empty());
622 ASSERT_TRUE(InitializeFile());
623 EXPECT_FALSE(base_file_
->full_path().empty());
625 // On Windows, CreateTemporaryFileInDir() will cause a path with short names
626 // to be expanded into a path with long names. Thus temp_dir.path() might not
627 // be a string-wise match to base_file_->full_path().DirName() even though
628 // they are in the same directory.
629 base::FilePath temp_file
;
630 ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_
.path(), &temp_file
));
631 ASSERT_FALSE(temp_file
.empty());
632 EXPECT_STREQ(temp_file
.DirName().value().c_str(),
633 base_file_
->full_path().DirName().value().c_str());
634 base_file_
->Finish();
637 } // namespace content