diff options
author | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2016-05-09 14:22:11 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2016-05-09 15:11:45 +0000 |
commit | 2ddb2d3e14eef3de7dbd0cef553d669b9ac2361c (patch) | |
tree | e75f511546c5fd1a173e87c1f9fb11d7ac8d1af3 /chromium/content/browser/download | |
parent | a4f3d46271c57e8155ba912df46a05559d14726e (diff) | |
download | qtwebengine-chromium-2ddb2d3e14eef3de7dbd0cef553d669b9ac2361c.tar.gz |
BASELINE: Update Chromium to 51.0.2704.41
Also adds in all smaller components by reversing logic for exclusion.
Change-Id: Ibf90b506e7da088ea2f65dcf23f2b0992c504422
Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
Diffstat (limited to 'chromium/content/browser/download')
53 files changed, 4677 insertions, 2408 deletions
diff --git a/chromium/content/browser/download/OWNERS b/chromium/content/browser/download/OWNERS index 9e1fc0676f3..27f3217e697 100644 --- a/chromium/content/browser/download/OWNERS +++ b/chromium/content/browser/download/OWNERS @@ -1,4 +1,2 @@ -ahendrickson@chromium.org asanka@chromium.org -phajdan.jr@chromium.org rdsmith@chromium.org diff --git a/chromium/content/browser/download/base_file.cc b/chromium/content/browser/download/base_file.cc index 8fda8de6a5c..74072bb1f72 100644 --- a/chromium/content/browser/download/base_file.cc +++ b/chromium/content/browser/download/base_file.cc @@ -25,38 +25,8 @@ namespace content { -// This will initialize the entire array to zero. -const unsigned char BaseFile::kEmptySha256Hash[] = { 0 }; - -BaseFile::BaseFile(const base::FilePath& full_path, - const GURL& source_url, - const GURL& referrer_url, - int64_t received_bytes, - bool calculate_hash, - const std::string& hash_state_bytes, - base::File file, - const net::BoundNetLog& bound_net_log) - : full_path_(full_path), - source_url_(source_url), - referrer_url_(referrer_url), - file_(std::move(file)), - bytes_so_far_(received_bytes), - start_tick_(base::TimeTicks::Now()), - calculate_hash_(calculate_hash), - detached_(false), - bound_net_log_(bound_net_log) { - memcpy(sha256_hash_, kEmptySha256Hash, crypto::kSHA256Length); - if (calculate_hash_) { - secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256)); - if ((bytes_so_far_ > 0) && // Not starting at the beginning. - (!IsEmptyHash(hash_state_bytes))) { - base::Pickle hash_state(hash_state_bytes.c_str(), - hash_state_bytes.size()); - base::PickleIterator data_iterator(hash_state); - secure_hash_->Deserialize(&data_iterator); - } - } -} +BaseFile::BaseFile(const net::BoundNetLog& bound_net_log) + : bound_net_log_(bound_net_log) {} BaseFile::~BaseFile() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); @@ -67,11 +37,16 @@ BaseFile::~BaseFile() { } DownloadInterruptReason BaseFile::Initialize( - const base::FilePath& default_directory) { + const base::FilePath& full_path, + const base::FilePath& default_directory, + base::File file, + int64_t bytes_so_far, + const std::string& hash_so_far, + scoped_ptr<crypto::SecureHash> hash_state) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); DCHECK(!detached_); - if (full_path_.empty()) { + if (full_path.empty()) { base::FilePath initial_directory(default_directory); base::FilePath temp_file; if (initial_directory.empty()) { @@ -87,9 +62,15 @@ DownloadInterruptReason BaseFile::Initialize( DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); } full_path_ = temp_file; + } else { + full_path_ = full_path; } - return Open(); + bytes_so_far_ = bytes_so_far; + secure_hash_ = std::move(hash_state); + file_ = std::move(file); + + return Open(hash_so_far); } DownloadInterruptReason BaseFile::AppendDataToFile(const char* data, @@ -134,7 +115,7 @@ DownloadInterruptReason BaseFile::AppendDataToFile(const char* data, RecordDownloadWriteSize(data_len); RecordDownloadWriteLoopCount(write_count); - if (calculate_hash_) + if (secure_hash_) secure_hash_->Update(data, data_len); return DOWNLOAD_INTERRUPT_REASON_NONE; @@ -170,7 +151,7 @@ DownloadInterruptReason BaseFile::Rename(const base::FilePath& new_path) { // reason. DownloadInterruptReason open_result = DOWNLOAD_INTERRUPT_REASON_NONE; if (was_in_progress) - open_result = Open(); + open_result = Open(std::string()); bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED); return rename_result == DOWNLOAD_INTERRUPT_REASON_NONE ? open_result @@ -198,92 +179,127 @@ void BaseFile::Cancel() { Detach(); } -void BaseFile::Finish() { - DCHECK_CURRENTLY_ON(BrowserThread::FILE); - - if (calculate_hash_) - secure_hash_->Finish(sha256_hash_, crypto::kSHA256Length); - Close(); -} - -void BaseFile::FinishWithError() { +scoped_ptr<crypto::SecureHash> BaseFile::Finish() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); Close(); -} - -void BaseFile::SetClientGuid(const std::string& guid) { - client_guid_ = guid; + return std::move(secure_hash_); } // OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations. #if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX) -DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() { +DownloadInterruptReason BaseFile::AnnotateWithSourceInformation( + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url) { return DOWNLOAD_INTERRUPT_REASON_NONE; } #endif -bool BaseFile::GetHash(std::string* hash) { - DCHECK(!detached_); - hash->assign(reinterpret_cast<const char*>(sha256_hash_), - sizeof(sha256_hash_)); - return (calculate_hash_ && !in_progress()); +std::string BaseFile::DebugString() const { + return base::StringPrintf( + "{ " + " full_path_ = \"%" PRFilePath + "\"" + " bytes_so_far_ = %" PRId64 " detached_ = %c }", + full_path_.value().c_str(), + bytes_so_far_, + detached_ ? 'T' : 'F'); } -std::string BaseFile::GetHashState() { - if (!calculate_hash_) - return std::string(); +DownloadInterruptReason BaseFile::CalculatePartialHash( + const std::string& hash_to_expect) { + secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256)); - base::Pickle hash_state; - if (!secure_hash_->Serialize(&hash_state)) - return std::string(); + if (bytes_so_far_ == 0) + return DOWNLOAD_INTERRUPT_REASON_NONE; - return std::string(reinterpret_cast<const char*>(hash_state.data()), - hash_state.size()); -} + if (file_.Seek(base::File::FROM_BEGIN, 0) != 0) + return LogSystemError("Seek partial file", + logging::GetLastSystemErrorCode()); + + const size_t kMinBufferSize = secure_hash_->GetHashLength(); + const size_t kMaxBufferSize = 1024 * 512; + + // The size of the buffer is: + // - at least kMinBufferSize so that we can use it to hold the hash as well. + // - at most kMaxBufferSize so that there's a reasonable bound. + // - not larger than |bytes_so_far_| unless bytes_so_far_ is less than the + // hash size. + std::vector<char> buffer(std::max( + kMinBufferSize, std::min<size_t>(kMaxBufferSize, bytes_so_far_))); + + int64_t current_position = 0; + while (current_position < bytes_so_far_) { + int length = file_.ReadAtCurrentPos(&buffer.front(), buffer.size()); + if (length == -1) { + return LogInterruptReason("Reading partial file", + logging::GetLastSystemErrorCode(), + DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT); + } -// static -bool BaseFile::IsEmptyHash(const std::string& hash) { - return (hash.size() == crypto::kSHA256Length && - 0 == memcmp(hash.data(), kEmptySha256Hash, crypto::kSHA256Length)); -} + if (length == 0) + break; -std::string BaseFile::DebugString() const { - return base::StringPrintf("{ source_url_ = \"%s\"" - " full_path_ = \"%" PRFilePath "\"" - " bytes_so_far_ = %" PRId64 - " detached_ = %c }", - source_url_.spec().c_str(), - full_path_.value().c_str(), - bytes_so_far_, - detached_ ? 'T' : 'F'); + secure_hash_->Update(&buffer.front(), length); + current_position += length; + } + + if (current_position != bytes_so_far_) { + return LogInterruptReason( + "Verifying prefix hash", 0, DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT); + } + + if (!hash_to_expect.empty()) { + DCHECK_EQ(secure_hash_->GetHashLength(), hash_to_expect.size()); + DCHECK(buffer.size() >= secure_hash_->GetHashLength()); + scoped_ptr<crypto::SecureHash> partial_hash(secure_hash_->Clone()); + partial_hash->Finish(&buffer.front(), buffer.size()); + + if (memcmp(&buffer.front(), + hash_to_expect.c_str(), + partial_hash->GetHashLength())) { + return LogInterruptReason("Verifying prefix hash", + 0, + DOWNLOAD_INTERRUPT_REASON_FILE_HASH_MISMATCH); + } + } + + return DOWNLOAD_INTERRUPT_REASON_NONE; } -DownloadInterruptReason BaseFile::Open() { +DownloadInterruptReason BaseFile::Open(const std::string& hash_so_far) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); DCHECK(!detached_); DCHECK(!full_path_.empty()); - bound_net_log_.BeginEvent( - net::NetLog::TYPE_DOWNLOAD_FILE_OPENED, - base::Bind(&FileOpenedNetLogCallback, &full_path_, bytes_so_far_)); - // Create a new file if it is not provided. if (!file_.IsValid()) { - file_.Initialize( - full_path_, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE); + file_.Initialize(full_path_, + base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE | + base::File::FLAG_READ); if (!file_.IsValid()) { - return LogNetError("Open", + return LogNetError("Open/Initialize File", net::FileErrorToNetError(file_.error_details())); } } - // We may be re-opening the file after rename. Always make sure we're - // writing at the end of the file. + bound_net_log_.BeginEvent( + net::NetLog::TYPE_DOWNLOAD_FILE_OPENED, + base::Bind(&FileOpenedNetLogCallback, &full_path_, bytes_so_far_)); + + if (!secure_hash_) { + DownloadInterruptReason reason = CalculatePartialHash(hash_so_far); + if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) { + ClearFile(); + return reason; + } + } + int64_t file_size = file_.Seek(base::File::FROM_END, 0); if (file_size < 0) { logging::SystemErrorCode error = logging::GetLastSystemErrorCode(); ClearFile(); - return LogSystemError("Seek", error); + return LogSystemError("Seeking to end", error); } else if (file_size > bytes_so_far_) { // The file is larger than we expected. // This is OK, as long as we don't use the extra. @@ -292,7 +308,7 @@ DownloadInterruptReason BaseFile::Open() { file_.Seek(base::File::FROM_BEGIN, bytes_so_far_) != bytes_so_far_) { logging::SystemErrorCode error = logging::GetLastSystemErrorCode(); ClearFile(); - return LogSystemError("Truncate", error); + return LogSystemError("Truncating to last known offset", error); } } else if (file_size < bytes_so_far_) { // The file is shorter than we expected. Our hashes won't be valid. @@ -347,6 +363,9 @@ DownloadInterruptReason BaseFile::LogInterruptReason( const char* operation, int os_error, DownloadInterruptReason reason) { + DVLOG(1) << __FUNCTION__ << "() operation:" << operation + << " os_error:" << os_error + << " reason:" << DownloadInterruptReasonToString(reason); bound_net_log_.AddEvent( net::NetLog::TYPE_DOWNLOAD_FILE_ERROR, base::Bind(&FileInterruptedNetLogCallback, operation, os_error, reason)); diff --git a/chromium/content/browser/download/base_file.h b/chromium/content/browser/download/base_file.h index cc87ae6ce67..139d43b9bb5 100644 --- a/chromium/content/browser/download/base_file.h +++ b/chromium/content/browser/download/base_file.h @@ -20,40 +20,72 @@ #include "base/time/time.h" #include "content/common/content_export.h" #include "content/public/browser/download_interrupt_reasons.h" -#include "crypto/sha2.h" +#include "crypto/secure_hash.h" #include "net/base/net_errors.h" #include "net/log/net_log.h" #include "url/gurl.h" -namespace crypto { -class SecureHash; -} - namespace content { // File being downloaded and saved to disk. This is a base class -// for DownloadFile and SaveFile, which keep more state information. +// for DownloadFile and SaveFile, which keep more state information. BaseFile +// considers itself the owner of the physical file and will delete it when the +// BaseFile object is destroyed unless the ownership is revoked via a call to +// Detach(). class CONTENT_EXPORT BaseFile { public: // May be constructed on any thread. All other routines (including // destruction) must occur on the FILE thread. - BaseFile(const base::FilePath& full_path, - const GURL& source_url, - const GURL& referrer_url, - int64_t received_bytes, - bool calculate_hash, - const std::string& hash_state, - base::File file, - const net::BoundNetLog& bound_net_log); - virtual ~BaseFile(); + BaseFile(const net::BoundNetLog& bound_net_log); + ~BaseFile(); // Returns DOWNLOAD_INTERRUPT_REASON_NONE on success, or a - // DownloadInterruptReason on failure. |default_directory| specifies the - // directory to create the temporary file in if |full_path()| is empty. If - // |default_directory| and |full_path()| are empty, then a temporary file will - // be created in the default download location as determined by - // ContentBrowserClient. - DownloadInterruptReason Initialize(const base::FilePath& default_directory); + // DownloadInterruptReason on failure. Upon success, the file at |full_path()| + // is assumed to be owned by the BaseFile. It will be deleted when the + // BaseFile object is destroyed unless Detach() is called before destroying + // the BaseFile instance. + // + // |full_path|: Full path to the download file. Can be empty, in which case + // the rules described in |default_directory| will be used to generate a + // temporary filename. + // + // |default_directory|: specifies the directory to create the temporary file + // in if |full_path| is empty. If |default_directory| and |full_path| are + // empty, then a temporary file will be created in the default download + // location as determined by ContentBrowserClient. + // + // |file|: The base::File handle to use. If specified, BaseFile will not open + // a file and will use this handle. The file should be opened for both + // read and write. Only makes sense if |full_path| is non-empty since it + // implies that the caller already knows the path to the file. There's no + // perfect way to come up with a canonical path for a file. So BaseFile + // will not attempt to determine the |full_path|. + // + // |bytes_so_far|: If a file is provided (via |full_path| or |file|), then + // this argument specifies the size of the file to expect. It is legal for + // the file to be larger, in which case the file will be truncated down to + // this size. However, if the file is shorter, then the operation will + // fail with DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT. + // + // |hash_so_far|: If |bytes_so_far| is non-zero, this specifies the SHA-256 + // hash of the first |bytes_so_far| bytes of the target file. If + // specified, BaseFile will read the first |bytes_so_far| of the target + // file in order to calculate the hash and verify that the file matches. + // If there's a mismatch, then the operation fails with + // DOWNLOAD_INTERRUPT_REASON_FILE_HASH_MISMATCH. Not used if |hash_state| + // is also specified. + // + // |hash_state|: The partial hash object to use. Only meaningful if there's a + // preexisting target file and it is non-empty (i.e. bytes_so_far is + // non-zero). If specified, BaseFile will assume that the bytes up to + // |bytes_so_far| has been accurately hashed into |hash_state| and will + // ignore |hash_so_far|. + DownloadInterruptReason Initialize(const base::FilePath& full_path, + const base::FilePath& default_directory, + base::File file, + int64_t bytes_so_far, + const std::string& hash_so_far, + scoped_ptr<crypto::SecureHash> hash_state); // Write a new chunk of data to the file. Returns a DownloadInterruptReason // indicating the result of the operation. @@ -63,33 +95,39 @@ class CONTENT_EXPORT BaseFile { // result of the operation. A return code of NONE indicates that the rename // was successful. After a failure, the full_path() and in_progress() can be // used to determine the last known filename and whether the file is available - // for writing or retrying the rename. - virtual DownloadInterruptReason Rename(const base::FilePath& full_path); - - // Detach the file so it is not deleted on destruction. - virtual void Detach(); - - // Abort the download and automatically close the file. + // for writing or retrying the rename. Call Finish() to obtain the last known + // hash state. + DownloadInterruptReason Rename(const base::FilePath& full_path); + + // Mark the file as detached. Up until this method is called, BaseFile assumes + // ownership of the file and hence will delete the file if the BaseFile object + // is destroyed. Calling Detach() causes BaseFile to assume that it no longer + // owns the file. Detach() can be called at any time. Close() must still be + // called to close the file if it is open. + void Detach(); + + // Abort the download and automatically close and delete the file. void Cancel(); // Indicate that the download has finished. No new data will be received. - void Finish(); - - // Indicate that the download is being aborted due to an error. This is - // identical to Finish() with the exception that the hash state will not be - // finalized. - void FinishWithError(); - - // Set the client guid which will be used to identify the app to the - // system AV scanning function. Should be called before - // AnnotateWithSourceInformation() to take effect. - void SetClientGuid(const std::string& guid); + // Returns the SecureHash object representing the state of the hash function + // at the end of the operation. + scoped_ptr<crypto::SecureHash> Finish(); // Informs the OS that this file came from the internet. Returns a // DownloadInterruptReason indicating the result of the operation. - // Note: SetClientGuid() should be called before this function on - // Windows to ensure the correct app client ID is available. - DownloadInterruptReason AnnotateWithSourceInformation(); + // + // |client_guid|: The client GUID which will be used to identify the caller to + // the system AV scanning function. + // + // |source_url| / |referrer_url|: Source and referrer for the network request + // that originated this download. Will be used to annotate source + // information and also to determine the relative danger level of the + // file. + DownloadInterruptReason AnnotateWithSourceInformation( + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url); // Returns the last known path to the download file. Can be empty if there's // no file. @@ -102,26 +140,27 @@ class CONTENT_EXPORT BaseFile { // Returns the number of bytes in the file pointed to by full_path(). int64_t bytes_so_far() const { return bytes_so_far_; } - // Fills |hash| with the hash digest for the file. - // Returns true if digest is successfully calculated. - virtual bool GetHash(std::string* hash); - - // Returns the current (intermediate) state of the hash as a byte string. - virtual std::string GetHashState(); - - // Returns true if the given hash is considered empty. An empty hash is - // a string of size crypto::kSHA256Length that contains only zeros (initial - // value for the hash). - static bool IsEmptyHash(const std::string& hash); - - virtual std::string DebugString() const; + std::string DebugString() const; private: friend class BaseFileTest; FRIEND_TEST_ALL_PREFIXES(BaseFileTest, IsEmptyHash); - // Creates and opens the file_ if it is NULL. - DownloadInterruptReason Open(); + // Creates and opens the file_ if it is invalid. + // + // If |hash_so_far| is not empty, then it must match the SHA-256 hash of the + // first |bytes_so_far_| bytes of |file_|. If there's a hash mismatch, Open() + // fails with DOWNLOAD_INTERRUPT_REASON_FILE_HASH_MISMATCH. + // + // If the opened file is shorter than |bytes_so_far_| bytes, then Open() fails + // with DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT. If the opened file is + // longer, then the file is truncated to |bytes_so_far_|. + // + // Open() can fail for other reasons as well. In that case, it returns a + // relevant interrupt reason. Unless Open() return + // DOWNLOAD_INTERRUPT_REASON_NONE, it should be assumed that |file_| is not + // valid. + DownloadInterruptReason Open(const std::string& hash_so_far); // Closes and resets file_. void Close(); @@ -138,6 +177,17 @@ class CONTENT_EXPORT BaseFile { // Split out from CurrentSpeed to enable testing. int64_t CurrentSpeedAtTime(base::TimeTicks current_time) const; + // Verifies that: + // * Size of the file represented by |file_| is at least |bytes_so_far_|. + // + // * If |hash_to_expect| is not empty, then the result of hashing the first + // |bytes_so_far_| bytes of |file_| matches |hash_to_expect|. + // + // If the result is REASON_NONE, then on return |secure_hash_| is valid and + // is ready to hash bytes from offset |bytes_so_far_| + 1. + DownloadInterruptReason CalculatePartialHash( + const std::string& hash_to_expect); + // Log a TYPE_DOWNLOAD_FILE_ERROR NetLog event with |error| and passes error // on through, converting to a |DownloadInterruptReason|. DownloadInterruptReason LogNetError(const char* operation, net::Error error); @@ -153,40 +203,24 @@ class CONTENT_EXPORT BaseFile { const char* operation, int os_error, DownloadInterruptReason reason); - static const unsigned char kEmptySha256Hash[crypto::kSHA256Length]; - // Full path to the file including the file name. base::FilePath full_path_; - // Source URL for the file being downloaded. - GURL source_url_; - - // The URL where the download was initiated. - GURL referrer_url_; - - std::string client_guid_; - // OS file for writing base::File file_; // Amount of data received up so far, in bytes. - int64_t bytes_so_far_; + int64_t bytes_so_far_ = 0; - // Start time for calculating speed. - base::TimeTicks start_tick_; - - // Indicates if hash should be calculated for the file. - bool calculate_hash_; - - // Used to calculate hash for the file when calculate_hash_ - // is set. + // Used to calculate hash for the file when calculate_hash_ is set. scoped_ptr<crypto::SecureHash> secure_hash_; - unsigned char sha256_hash_[crypto::kSHA256Length]; + // Start time for calculating speed. + base::TimeTicks start_tick_; // Indicates that this class no longer owns the associated file, and so // won't delete it on destruction. - bool detached_; + bool detached_ = false; net::BoundNetLog bound_net_log_; diff --git a/chromium/content/browser/download/base_file_linux.cc b/chromium/content/browser/download/base_file_linux.cc index 721cd602694..6c62be32302 100644 --- a/chromium/content/browser/download/base_file_linux.cc +++ b/chromium/content/browser/download/base_file_linux.cc @@ -9,11 +9,14 @@ namespace content { -DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() { +DownloadInterruptReason BaseFile::AnnotateWithSourceInformation( + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); DCHECK(!detached_); - AddOriginMetadataToFile(full_path_, source_url_, referrer_url_); + AddOriginMetadataToFile(full_path_, source_url, referrer_url); return DOWNLOAD_INTERRUPT_REASON_NONE; } diff --git a/chromium/content/browser/download/base_file_mac.cc b/chromium/content/browser/download/base_file_mac.cc index f3edf12d094..4ece61b90d7 100644 --- a/chromium/content/browser/download/base_file_mac.cc +++ b/chromium/content/browser/download/base_file_mac.cc @@ -9,12 +9,15 @@ namespace content { -DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() { +DownloadInterruptReason BaseFile::AnnotateWithSourceInformation( + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); DCHECK(!detached_); - AddQuarantineMetadataToFile(full_path_, source_url_, referrer_url_); - AddOriginMetadataToFile(full_path_, source_url_, referrer_url_); + AddQuarantineMetadataToFile(full_path_, source_url, referrer_url); + AddOriginMetadataToFile(full_path_, source_url, referrer_url); return DOWNLOAD_INTERRUPT_REASON_NONE; } diff --git a/chromium/content/browser/download/base_file_unittest.cc b/chromium/content/browser/download/base_file_unittest.cc index a6ce347c189..8f2412b17d9 100644 --- a/chromium/content/browser/download/base_file_unittest.cc +++ b/chromium/content/browser/download/base_file_unittest.cc @@ -32,12 +32,24 @@ const char kTestData3[] = "Final line."; const char kTestData4[] = "supercalifragilisticexpialidocious"; const int kTestDataLength1 = arraysize(kTestData1) - 1; const int kTestDataLength2 = arraysize(kTestData2) - 1; -const int kTestDataLength3 = arraysize(kTestData3) - 1; const int kTestDataLength4 = arraysize(kTestData4) - 1; const int kElapsedTimeSeconds = 5; const base::TimeDelta kElapsedTimeDelta = base::TimeDelta::FromSeconds( kElapsedTimeSeconds); +// SHA-256 hash of kTestData1 (excluding terminating NUL). +const uint8_t kHashOfTestData1[] = { + 0x0b, 0x2d, 0x3f, 0x3f, 0x79, 0x43, 0xad, 0x64, 0xb8, 0x60, 0xdf, + 0x94, 0xd0, 0x5c, 0xb5, 0x6a, 0x8a, 0x97, 0xc6, 0xec, 0x57, 0x68, + 0xb5, 0xb7, 0x0b, 0x93, 0x0c, 0x5a, 0xa7, 0xfa, 0x9a, 0xde}; + +// SHA-256 hash of kTestData1 ++ kTestData2 ++ kTestData3 (excluding terminating +// NUL). +const uint8_t kHashOfTestData1To3[] = { + 0xcb, 0xf6, 0x8b, 0xf1, 0x0f, 0x80, 0x03, 0xdb, 0x86, 0xb3, 0x13, + 0x43, 0xaf, 0xac, 0x8c, 0x71, 0x75, 0xbd, 0x03, 0xfb, 0x5f, 0xc9, + 0x05, 0x65, 0x0f, 0x8c, 0x80, 0xaf, 0x08, 0x74, 0x43, 0xa8}; + } // namespace class BaseFileTest : public testing::Test { @@ -52,16 +64,8 @@ class BaseFileTest : public testing::Test { } void SetUp() override { - ResetHash(); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - base_file_.reset(new BaseFile(base::FilePath(), - GURL(), - GURL(), - 0, - false, - std::string(), - base::File(), - net::BoundNetLog())); + base_file_.reset(new BaseFile(net::BoundNetLog())); } void TearDown() override { @@ -87,36 +91,14 @@ class BaseFileTest : public testing::Test { EXPECT_EQ(expect_file_survives_, base::PathExists(full_path)); } - void ResetHash() { - secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256)); - memcpy(sha256_hash_, kEmptySha256Hash, crypto::kSHA256Length); - } - - void UpdateHash(const char* data, size_t length) { - secure_hash_->Update(data, length); - } - - std::string GetFinalHash() { - std::string hash; - secure_hash_->Finish(sha256_hash_, crypto::kSHA256Length); - hash.assign(reinterpret_cast<const char*>(sha256_hash_), - sizeof(sha256_hash_)); - return hash; - } - - void MakeFileWithHash() { - base_file_.reset(new BaseFile(base::FilePath(), - GURL(), - GURL(), - 0, - true, - std::string(), - base::File(), - net::BoundNetLog())); - } - bool InitializeFile() { - DownloadInterruptReason result = base_file_->Initialize(temp_dir_.path()); + DownloadInterruptReason result = + base_file_->Initialize(base::FilePath(), + temp_dir_.path(), + base::File(), + 0, + std::string(), + scoped_ptr<crypto::SecureHash>()); EXPECT_EQ(expected_error_, result); return result == DOWNLOAD_INTERRUPT_REASON_NONE; } @@ -145,17 +127,15 @@ class BaseFileTest : public testing::Test { // Create a file. Returns the complete file path. base::FilePath CreateTestFile() { base::FilePath file_name; - BaseFile file(base::FilePath(), - GURL(), - GURL(), - 0, - false, - std::string(), - base::File(), - net::BoundNetLog()); + BaseFile file((net::BoundNetLog())); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, - file.Initialize(temp_dir_.path())); + file.Initialize(base::FilePath(), + temp_dir_.path(), + base::File(), + 0, + std::string(), + scoped_ptr<crypto::SecureHash>())); file_name = file.full_path(); EXPECT_NE(base::FilePath::StringType(), file_name.value()); @@ -171,16 +151,14 @@ class BaseFileTest : public testing::Test { // Create a file with the specified file name. void CreateFileWithName(const base::FilePath& file_name) { EXPECT_NE(base::FilePath::StringType(), file_name.value()); - BaseFile duplicate_file(file_name, - GURL(), - GURL(), - 0, - false, - std::string(), - base::File(), - net::BoundNetLog()); + BaseFile duplicate_file((net::BoundNetLog())); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, - duplicate_file.Initialize(temp_dir_.path())); + duplicate_file.Initialize(file_name, + temp_dir_.path(), + base::File(), + 0, + std::string(), + scoped_ptr<crypto::SecureHash>())); // Write something into it. duplicate_file.AppendDataToFile(kTestData4, kTestDataLength4); // Detach the file so it isn't deleted on destruction of |duplicate_file|. @@ -207,6 +185,15 @@ class BaseFileTest : public testing::Test { << "Interrupt reason = " << err; } + template <size_t SZ> + static void ExpectHashValue(const uint8_t (&expected_hash)[SZ], + scoped_ptr<crypto::SecureHash> hash_state) { + std::vector<uint8_t> hash_value(hash_state->GetHashLength()); + hash_state->Finish(&hash_value.front(), hash_value.size()); + ASSERT_EQ(SZ, hash_value.size()); + EXPECT_EQ(0, memcmp(expected_hash, &hash_value.front(), hash_value.size())); + } + protected: // BaseClass instance we are testing. scoped_ptr<BaseFile> base_file_; @@ -220,11 +207,6 @@ class BaseFileTest : public testing::Test { // Expect the file to be in progress. bool expect_in_progress_; - // Hash calculator. - scoped_ptr<crypto::SecureHash> secure_hash_; - - unsigned char sha256_hash_[crypto::kSHA256Length]; - private: // Keep track of what data should be saved to the disk file. std::string expected_data_; @@ -266,24 +248,9 @@ TEST_F(BaseFileTest, WriteAndDetach) { // Write data to the file and detach it, and calculate its sha256 hash. TEST_F(BaseFileTest, WriteWithHashAndDetach) { - // Calculate the final hash. - ResetHash(); - UpdateHash(kTestData1, kTestDataLength1); - std::string expected_hash = GetFinalHash(); - std::string expected_hash_hex = - base::HexEncode(expected_hash.data(), expected_hash.size()); - - MakeFileWithHash(); ASSERT_TRUE(InitializeFile()); ASSERT_TRUE(AppendDataToFile(kTestData1)); - base_file_->Finish(); - - std::string hash; - base_file_->GetHash(&hash); - EXPECT_EQ("0B2D3F3F7943AD64B860DF94D05CB56A8A97C6EC5768B5B70B930C5AA7FA9ADE", - expected_hash_hex); - EXPECT_EQ(expected_hash_hex, base::HexEncode(hash.data(), hash.size())); - + ExpectHashValue(kHashOfTestData1, base_file_->Finish()); base_file_->Detach(); expect_file_survives_ = true; } @@ -303,7 +270,7 @@ TEST_F(BaseFileTest, WriteThenRenameAndDetach) { EXPECT_FALSE(base::PathExists(initial_path)); EXPECT_TRUE(base::PathExists(new_path)); - base_file_->Finish(); + ExpectHashValue(kHashOfTestData1, base_file_->Finish()); base_file_->Detach(); expect_file_survives_ = true; } @@ -312,7 +279,7 @@ TEST_F(BaseFileTest, WriteThenRenameAndDetach) { TEST_F(BaseFileTest, SingleWrite) { ASSERT_TRUE(InitializeFile()); ASSERT_TRUE(AppendDataToFile(kTestData1)); - base_file_->Finish(); + ExpectHashValue(kHashOfTestData1, base_file_->Finish()); } // Write data to the file multiple times. @@ -321,82 +288,18 @@ TEST_F(BaseFileTest, MultipleWrites) { ASSERT_TRUE(AppendDataToFile(kTestData1)); ASSERT_TRUE(AppendDataToFile(kTestData2)); ASSERT_TRUE(AppendDataToFile(kTestData3)); - std::string hash; - EXPECT_FALSE(base_file_->GetHash(&hash)); - base_file_->Finish(); -} - -// Write data to the file once and calculate its sha256 hash. -TEST_F(BaseFileTest, SingleWriteWithHash) { - // Calculate the final hash. - ResetHash(); - UpdateHash(kTestData1, kTestDataLength1); - std::string expected_hash = GetFinalHash(); - std::string expected_hash_hex = - base::HexEncode(expected_hash.data(), expected_hash.size()); - - MakeFileWithHash(); - ASSERT_TRUE(InitializeFile()); - // Can get partial hash states before Finish() is called. - EXPECT_STRNE(std::string().c_str(), base_file_->GetHashState().c_str()); - ASSERT_TRUE(AppendDataToFile(kTestData1)); - EXPECT_STRNE(std::string().c_str(), base_file_->GetHashState().c_str()); - base_file_->Finish(); - - std::string hash; - base_file_->GetHash(&hash); - EXPECT_EQ(expected_hash_hex, base::HexEncode(hash.data(), hash.size())); -} - -// Write data to the file multiple times and calculate its sha256 hash. -TEST_F(BaseFileTest, MultipleWritesWithHash) { - // Calculate the final hash. - ResetHash(); - UpdateHash(kTestData1, kTestDataLength1); - UpdateHash(kTestData2, kTestDataLength2); - UpdateHash(kTestData3, kTestDataLength3); - std::string expected_hash = GetFinalHash(); - std::string expected_hash_hex = - base::HexEncode(expected_hash.data(), expected_hash.size()); - - std::string hash; - MakeFileWithHash(); - ASSERT_TRUE(InitializeFile()); - ASSERT_TRUE(AppendDataToFile(kTestData1)); - ASSERT_TRUE(AppendDataToFile(kTestData2)); - ASSERT_TRUE(AppendDataToFile(kTestData3)); - // No hash before Finish() is called. - EXPECT_FALSE(base_file_->GetHash(&hash)); - base_file_->Finish(); - - EXPECT_TRUE(base_file_->GetHash(&hash)); - EXPECT_EQ("CBF68BF10F8003DB86B31343AFAC8C7175BD03FB5FC905650F8C80AF087443A8", - expected_hash_hex); - EXPECT_EQ(expected_hash_hex, base::HexEncode(hash.data(), hash.size())); + ExpectHashValue(kHashOfTestData1To3, base_file_->Finish()); } // Write data to the file multiple times, interrupt it, and continue using // another file. Calculate the resulting combined sha256 hash. TEST_F(BaseFileTest, MultipleWritesInterruptedWithHash) { - // Calculate the final hash. - ResetHash(); - UpdateHash(kTestData1, kTestDataLength1); - UpdateHash(kTestData2, kTestDataLength2); - UpdateHash(kTestData3, kTestDataLength3); - std::string expected_hash = GetFinalHash(); - std::string expected_hash_hex = - base::HexEncode(expected_hash.data(), expected_hash.size()); - - MakeFileWithHash(); ASSERT_TRUE(InitializeFile()); // Write some data ASSERT_TRUE(AppendDataToFile(kTestData1)); ASSERT_TRUE(AppendDataToFile(kTestData2)); // Get the hash state and file name. - std::string hash_state; - hash_state = base_file_->GetHashState(); - // Finish the file. - base_file_->Finish(); + scoped_ptr<crypto::SecureHash> hash_state = base_file_->Finish(); base::FilePath new_file_path(temp_dir_.path().Append( base::FilePath(FILE_PATH_LITERAL("second_file")))); @@ -404,26 +307,18 @@ TEST_F(BaseFileTest, MultipleWritesInterruptedWithHash) { ASSERT_TRUE(base::CopyFile(base_file_->full_path(), new_file_path)); // Create another file - BaseFile second_file(new_file_path, - GURL(), - GURL(), - base_file_->bytes_so_far(), - true, - hash_state, - base::File(), - net::BoundNetLog()); + BaseFile second_file((net::BoundNetLog())); ASSERT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, - second_file.Initialize(base::FilePath())); + second_file.Initialize(new_file_path, + base::FilePath(), + base::File(), + base_file_->bytes_so_far(), + std::string(), + std::move(hash_state))); std::string data(kTestData3); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, second_file.AppendDataToFile(data.data(), data.size())); - second_file.Finish(); - - std::string hash; - EXPECT_TRUE(second_file.GetHash(&hash)); - // This will fail until getting the hash state is supported in SecureHash. - EXPECT_STREQ(expected_hash_hex.c_str(), - base::HexEncode(hash.data(), hash.size()).c_str()); + ExpectHashValue(kHashOfTestData1To3, second_file.Finish()); } // Rename the file after all writes to it. @@ -442,7 +337,7 @@ TEST_F(BaseFileTest, WriteThenRename) { EXPECT_FALSE(base::PathExists(initial_path)); EXPECT_TRUE(base::PathExists(new_path)); - base_file_->Finish(); + ExpectHashValue(kHashOfTestData1, base_file_->Finish()); } // Rename the file while the download is still in progress. @@ -462,8 +357,9 @@ TEST_F(BaseFileTest, RenameWhileInProgress) { EXPECT_TRUE(base::PathExists(new_path)); ASSERT_TRUE(AppendDataToFile(kTestData2)); + ASSERT_TRUE(AppendDataToFile(kTestData3)); - base_file_->Finish(); + ExpectHashValue(kHashOfTestData1To3, base_file_->Finish()); } // Test that a failed rename reports the correct error. @@ -525,15 +421,7 @@ TEST_F(BaseFileTest, RenameWithErrorInProgress) { ASSERT_EQ(new_path.value(), base_file_->full_path().value()); ASSERT_TRUE(AppendDataToFile(kTestData3)); - base_file_->Finish(); - - // The contents of the file should be intact. - std::string file_contents; - std::string expected_contents(kTestData1); - expected_contents += kTestData2; - expected_contents += kTestData3; - ASSERT_TRUE(base::ReadFileToString(new_path, &file_contents)); - EXPECT_EQ(expected_contents, file_contents); + ExpectHashValue(kHashOfTestData1To3, base_file_->Finish()); } // Test that a failed write reports an error. @@ -544,9 +432,14 @@ TEST_F(BaseFileTest, WriteWithError) { // Pass a file handle which was opened without the WRITE flag. // This should result in an error when writing. base::File file(path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ); - base_file_.reset(new BaseFile(path, GURL(), GURL(), 0, false, std::string(), - std::move(file), net::BoundNetLog())); - ASSERT_TRUE(InitializeFile()); + base_file_.reset(new BaseFile(net::BoundNetLog())); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, + base_file_->Initialize(path, + base::FilePath(), + std::move(file), + 0, + std::string(), + scoped_ptr<crypto::SecureHash>())); #if defined(OS_WIN) set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED); #elif defined (OS_POSIX) @@ -580,20 +473,17 @@ TEST_F(BaseFileTest, DuplicateBaseFile) { TEST_F(BaseFileTest, AppendToBaseFile) { // Create a new file. base::FilePath existing_file_name = CreateTestFile(); - set_expected_data(kTestData4); // Use the file we've just created. - base_file_.reset(new BaseFile(existing_file_name, - GURL(), - GURL(), - kTestDataLength4, - false, - std::string(), - base::File(), - net::BoundNetLog())); - - ASSERT_TRUE(InitializeFile()); + base_file_.reset(new BaseFile(net::BoundNetLog())); + ASSERT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, + base_file_->Initialize(existing_file_name, + base::FilePath(), + base::File(), + kTestDataLength4, + std::string(), + scoped_ptr<crypto::SecureHash>())); const base::FilePath file_name = base_file_->full_path(); EXPECT_NE(base::FilePath::StringType(), file_name.value()); @@ -618,18 +508,16 @@ TEST_F(BaseFileTest, ReadonlyBaseFile) { EXPECT_TRUE(base::MakeFileUnwritable(readonly_file_name)); // Try to overwrite it. - base_file_.reset(new BaseFile(readonly_file_name, - GURL(), - GURL(), - 0, - false, - std::string(), - base::File(), - net::BoundNetLog())); + base_file_.reset(new BaseFile(net::BoundNetLog())); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED, + base_file_->Initialize(readonly_file_name, + base::FilePath(), + base::File(), + 0, + std::string(), + scoped_ptr<crypto::SecureHash>())); expect_in_progress_ = false; - set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED); - EXPECT_FALSE(InitializeFile()); const base::FilePath file_name = base_file_->full_path(); EXPECT_NE(base::FilePath::StringType(), file_name.value()); @@ -643,16 +531,175 @@ TEST_F(BaseFileTest, ReadonlyBaseFile) { expect_file_survives_ = true; } -TEST_F(BaseFileTest, IsEmptyHash) { - std::string empty(crypto::kSHA256Length, '\x00'); - EXPECT_TRUE(BaseFile::IsEmptyHash(empty)); - std::string not_empty(crypto::kSHA256Length, '\x01'); - EXPECT_FALSE(BaseFile::IsEmptyHash(not_empty)); - EXPECT_FALSE(BaseFile::IsEmptyHash(std::string())); +// Open an existing file and continue writing to it. The hash of the partial +// file is known and matches the existing contents. +TEST_F(BaseFileTest, ExistingBaseFileKnownHash) { + base::FilePath file_path = temp_dir_.path().AppendASCII("existing"); + ASSERT_TRUE(base::WriteFile(file_path, kTestData1, kTestDataLength1)); + + std::string hash_so_far(std::begin(kHashOfTestData1), + std::end(kHashOfTestData1)); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, + base_file_->Initialize(file_path, + base::FilePath(), + base::File(), + kTestDataLength1, + hash_so_far, + scoped_ptr<crypto::SecureHash>())); + set_expected_data(kTestData1); + ASSERT_TRUE(AppendDataToFile(kTestData2)); + ASSERT_TRUE(AppendDataToFile(kTestData3)); + ExpectHashValue(kHashOfTestData1To3, base_file_->Finish()); +} + +// Open an existing file and continue writing to it. The hash of the partial +// file is unknown. +TEST_F(BaseFileTest, ExistingBaseFileUnknownHash) { + base::FilePath file_path = temp_dir_.path().AppendASCII("existing"); + ASSERT_TRUE(base::WriteFile(file_path, kTestData1, kTestDataLength1)); + + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, + base_file_->Initialize(file_path, + base::FilePath(), + base::File(), + kTestDataLength1, + std::string(), + scoped_ptr<crypto::SecureHash>())); + set_expected_data(kTestData1); + ASSERT_TRUE(AppendDataToFile(kTestData2)); + ASSERT_TRUE(AppendDataToFile(kTestData3)); + ExpectHashValue(kHashOfTestData1To3, base_file_->Finish()); +} + +// Open an existing file. The contentsof the file doesn't match the known hash. +TEST_F(BaseFileTest, ExistingBaseFileIncorrectHash) { + base::FilePath file_path = temp_dir_.path().AppendASCII("existing"); + ASSERT_TRUE(base::WriteFile(file_path, kTestData2, kTestDataLength2)); + + std::string hash_so_far(std::begin(kHashOfTestData1), + std::end(kHashOfTestData1)); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_HASH_MISMATCH, + base_file_->Initialize(file_path, + base::FilePath(), + base::File(), + kTestDataLength2, + hash_so_far, + scoped_ptr<crypto::SecureHash>())); + set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_HASH_MISMATCH); +} - std::string also_not_empty = empty; - also_not_empty[crypto::kSHA256Length - 1] = '\x01'; - EXPECT_FALSE(BaseFile::IsEmptyHash(also_not_empty)); +// Open a large existing file with a known hash and continue writing to it. +TEST_F(BaseFileTest, ExistingBaseFileLargeSizeKnownHash) { + base::FilePath file_path = temp_dir_.path().AppendASCII("existing"); + std::string big_buffer(1024 * 200, 'a'); + ASSERT_TRUE(base::WriteFile(file_path, big_buffer.data(), big_buffer.size())); + + // Hash of partial file (1024*200 * 'a') + const uint8_t kExpectedPartialHash[] = { + 0x4b, 0x4f, 0x0f, 0x46, 0xac, 0x02, 0xd1, 0x77, 0xde, 0xa0, 0xab, + 0x36, 0xa6, 0x6a, 0x65, 0x78, 0x40, 0xe2, 0xfb, 0x98, 0xb2, 0x0b, + 0xb2, 0x7a, 0x68, 0x8d, 0xb4, 0xd8, 0xea, 0x9c, 0xd2, 0x2c}; + + // Hash of entire file (1024*400 * 'a') + const uint8_t kExpectedFullHash[] = { + 0x0c, 0xe9, 0xf6, 0x78, 0x6b, 0x0f, 0x58, 0x49, 0x36, 0xe8, 0x83, + 0xc5, 0x09, 0x16, 0xbc, 0x5e, 0x2d, 0x07, 0x95, 0xb9, 0x42, 0x20, + 0x41, 0x7c, 0xb3, 0x38, 0xd3, 0xf4, 0xe0, 0x78, 0x89, 0x46}; + + ASSERT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, + base_file_->Initialize(file_path, + base::FilePath(), + base::File(), + big_buffer.size(), + std::string(std::begin(kExpectedPartialHash), + std::end(kExpectedPartialHash)), + scoped_ptr<crypto::SecureHash>())); + set_expected_data(big_buffer); // Contents of the file on Open. + ASSERT_TRUE(AppendDataToFile(big_buffer)); + ExpectHashValue(kExpectedFullHash, base_file_->Finish()); +} + +// Open a large existing file. The contents doesn't match the known hash. +TEST_F(BaseFileTest, ExistingBaseFileLargeSizeIncorrectHash) { + base::FilePath file_path = temp_dir_.path().AppendASCII("existing"); + std::string big_buffer(1024 * 200, 'a'); + ASSERT_TRUE(base::WriteFile(file_path, big_buffer.data(), big_buffer.size())); + + // Incorrect hash of partial file (1024*200 * 'a') + const uint8_t kExpectedPartialHash[] = { + 0xc2, 0xa9, 0x08, 0xd9, 0x8f, 0x5d, 0xf9, 0x87, 0xad, 0xe4, 0x1b, + 0x5f, 0xce, 0x21, 0x30, 0x67, 0xef, 0x6c, 0xc2, 0x1e, 0xf2, 0x24, + 0x02, 0x12, 0xa4, 0x1e, 0x54, 0xb5, 0xe7, 0xc2, 0x8a, 0xe5}; + + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_HASH_MISMATCH, + base_file_->Initialize(file_path, + base::FilePath(), + base::File(), + big_buffer.size(), + std::string(std::begin(kExpectedPartialHash), + std::end(kExpectedPartialHash)), + scoped_ptr<crypto::SecureHash>())); + set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_HASH_MISMATCH); +} + +// Open an existing file. The size of the file is too short. +TEST_F(BaseFileTest, ExistingBaseFileTooShort) { + base::FilePath file_path = temp_dir_.path().AppendASCII("existing"); + ASSERT_TRUE(base::WriteFile(file_path, kTestData1, kTestDataLength1)); + + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT, + base_file_->Initialize(file_path, + base::FilePath(), + base::File(), + kTestDataLength1 + 1, + std::string(), + scoped_ptr<crypto::SecureHash>())); + set_expected_error(DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT); +} + +// Open an existing file. The size is larger than expected. +TEST_F(BaseFileTest, ExistingBaseFileKnownHashTooLong) { + base::FilePath file_path = temp_dir_.path().AppendASCII("existing"); + std::string contents; + contents.append(kTestData1); + contents.append("Something extra"); + ASSERT_TRUE(base::WriteFile(file_path, contents.data(), contents.size())); + + std::string hash_so_far(std::begin(kHashOfTestData1), + std::end(kHashOfTestData1)); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, + base_file_->Initialize(file_path, + base::FilePath(), + base::File(), + kTestDataLength1, + hash_so_far, + scoped_ptr<crypto::SecureHash>())); + set_expected_data(kTestData1); // Our starting position. + ASSERT_TRUE(AppendDataToFile(kTestData2)); + ASSERT_TRUE(AppendDataToFile(kTestData3)); + ExpectHashValue(kHashOfTestData1To3, base_file_->Finish()); +} + +// Open an existing file. The size is large than expected and the hash is +// unknown. +TEST_F(BaseFileTest, ExistingBaseFileUnknownHashTooLong) { + base::FilePath file_path = temp_dir_.path().AppendASCII("existing"); + std::string contents; + contents.append(kTestData1); + contents.append("Something extra"); + ASSERT_TRUE(base::WriteFile(file_path, contents.data(), contents.size())); + + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, + base_file_->Initialize(file_path, + base::FilePath(), + base::File(), + kTestDataLength1, + std::string(), + scoped_ptr<crypto::SecureHash>())); + set_expected_data(kTestData1); + ASSERT_TRUE(AppendDataToFile(kTestData2)); + ASSERT_TRUE(AppendDataToFile(kTestData3)); + ExpectHashValue(kHashOfTestData1To3, base_file_->Finish()); } // Test that a temporary file is created in the default download directory. diff --git a/chromium/content/browser/download/base_file_win.cc b/chromium/content/browser/download/base_file_win.cc index bfdf3aa9da6..5fa373a43ec 100644 --- a/chromium/content/browser/download/base_file_win.cc +++ b/chromium/content/browser/download/base_file_win.cc @@ -352,22 +352,25 @@ DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions( return interrupt_reason; } -DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() { +DownloadInterruptReason BaseFile::AnnotateWithSourceInformation( + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); DCHECK(!detached_); bound_net_log_.BeginEvent(net::NetLog::TYPE_DOWNLOAD_FILE_ANNOTATED); DownloadInterruptReason result = DOWNLOAD_INTERRUPT_REASON_NONE; - std::string braces_guid = "{" + client_guid_ + "}"; + std::string braces_guid = "{" + client_guid + "}"; GUID guid = GUID_NULL; - if (base::IsValidGUID(client_guid_)) { + if (base::IsValidGUID(client_guid)) { HRESULT hr = CLSIDFromString( base::UTF8ToUTF16(braces_guid).c_str(), &guid); if (FAILED(hr)) guid = GUID_NULL; } - HRESULT hr = AVScanFile(full_path_, source_url_.spec(), guid); + HRESULT hr = AVScanFile(full_path_, source_url.spec(), guid); // If the download file is missing after the call, then treat this as an // interrupted download. diff --git a/chromium/content/browser/download/docs/save-page-as.md b/chromium/content/browser/download/docs/save-page-as.md new file mode 100644 index 00000000000..2661289b375 --- /dev/null +++ b/chromium/content/browser/download/docs/save-page-as.md @@ -0,0 +1,137 @@ +# High-level overview of Save-Page-As code + +This document describes code under `//content/browser/downloads` +restricting the scope only to code handling Save-Page-As functionality +(i.e. leaving out other downloads-related code). +This document focuses on high-level overview and aspects of the code that +span multiple compilation units (hoping that individual compilation units +are described by their code comments or by their code structure). + +## Classes overview + +* SavePackage class + * coordinates overall save-page-as request + * created and owned by `WebContents` + (ref-counted today, but it is unnecessary - see https://crbug.com/596953) + * UI-thread object + +* SaveFileCreateInfo::SaveFileSource enum + * classifies `SaveItem` and `SaveFile` processing into 3 flavours: + * `SAVE_FILE_FROM_NET` (see `SaveFileResourceHandler`) + * `SAVE_FILE_FROM_DOM` (see "Complete HTML" section below) + * `SAVE_FILE_FROM_FILE` (see `SaveFileManager::SaveLocalFile`) + +* SaveItem class + * tracks saving a single file + * created and owned by `SavePackage` + * UI-thread object + +* SaveFileManager class + * coordinates between FILE and UI threads + * Gets requests from `SavePackage` and communicates results back to + `SavePackage` on the UI thread. + * Shephards data (received from the network OR from DOM) into + FILE thread - via `SaveFileManager::UpdateSaveProgress` + * created and owned by `ResourceDispatchedHostImpl` + (ref-counted today, but it is unnecessary - see https://crbug.com/596953) + +* SaveFile class + * tracks saving a single file + * created and owned by `SaveFileManager` + * FILE-thread object + +* SaveFileResourceHandler class + * tracks network downloads + forwards their status into `SaveFileManager` + (onto FILE-thread) + * created by `ResourceDispatcherHostImpl::BeginSaveFile` + * IO-thread object + +* SaveFileCreateInfo POD struct + * short-lived object holding data passed to callbacks handling start of + saving a file. + +* MHTMLGenerationManager class + * singleton that manages progress of jobs responsible for saving individual + MHTML files (represented by `MHTMLGenerationManager::Job`). + + +## Overview of the processing flow + +Save-Page-As flow starts with `WebContents::OnSavePage`. +The flow is different depending on the save format chosen by the user +(each flow is described in a separate section below). + +### Complete HTML + +Very high-level flow of saving a page as "Complete HTML": + +* Step 1: `SavePackage` asks all frames for "savable resources" + and creates `SaveItem` for each of files that need to be saved + +* Step 2: `SavePackage` first processes `SAVE_FILE_FROM_NET` and + `SAVE_FILE_FROM_FILE` `SaveItem`s and asks `SaveFileManager` to save + them. + +* Step 3: `SavePackage` handles remaining `SAVE_FILE_FROM_DOM` `SaveItem`s and + asks each frame to serialize its DOM/HTML (each frame gets from + `SavePackage` a map covering local paths that need to be referenced by + the frame). Responses from frames get forwarded to `SaveFileManager` + to be written to disk. + + +### MHTML + +Very high-level flow of saving a page as MHTML: + +* Step 1: `WebContents::GenerateMHTML` is called by either `SavePackage` (for + Save-Page-As UI) or Extensions (via `chrome.pageCapture` extensions + API) or by an embedder of `WebContents` (since this is public API of + //content). + +* Step 2: `MHTMLGenerationManager` coordinates generation of the MHTML file + by sequentially (one-at-a-time) asking each frame to write its portion + of MHTML to a file handle. Other classes (i.e. `SavePackage` and/or + `SaveFileManager`) are not used at this step at all. + +* Step 3: When done `MHTMLGenerationManager` calls a completion callback + which in case of Save-Page-As will end up in + `SavePackage::OnMHTMLGenerated`. + +Note: MHTML format is by default disabled in Save-Page-As UI on Windows, MacOS +and Linux (it is the default on ChromeOS), but for testing this can be easily +changed using `--save-page-as-mhtml` command line switch. + + +### HTML Only + +Very high-level flow of saving a page as "HTML Only": + +* `SavePackage` creates only a single `SaveItem` (either `SAVE_FILE_FROM_NET` or + `SAVE_FILE_FROM_FILE`) and asks `SaveFileManager` to process it + (as in the Complete HTML individual SaveItem handling above.). + + +## Other relevant code + +Pointers to related code outside of `//content/browser/download`: + +* End-to-end tests: + * `//chrome/browser/downloads/save_page_browsertest.cc` + * `//chrome/test/data/save_page/...` + +* Other tests: + * `//content/browser/downloads/*test*.cc` + * `//content/renderer/dom_serializer_browsertest.cc` - single process... :-/ + +* Elsewhere in `//content`: + * `//content/renderer/savable_resources...` + +* Blink: + * `//third_party/WebKit/public/web/WebFrameSerializer...` + * `//third_party/WebKit/Source/web/WebFrameSerializerImpl...` + (used for Complete HTML today; should use `FrameSerializer` instead in + the long-term - see https://crbug.com/328354). + * `//third_party/WebKit/Source/core/frame/FrameSerializer...` + (used for MHTML today) + * `//third_party/WebKit/Source/platform/mhtml/MHTMLArchive...` + diff --git a/chromium/content/browser/download/download_browsertest.cc b/chromium/content/browser/download/download_browsertest.cc index dc965d41646..19a05a2d6a0 100644 --- a/chromium/content/browser/download/download_browsertest.cc +++ b/chromium/content/browser/download/download_browsertest.cc @@ -29,8 +29,12 @@ #include "content/browser/download/download_item_impl.h" #include "content/browser/download/download_manager_impl.h" #include "content/browser/download/download_resource_handler.h" +#include "content/browser/loader/resource_dispatcher_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/browser/download_danger_type.h" #include "content/public/browser/power_save_blocker.h" +#include "content/public/browser/resource_dispatcher_host_delegate.h" +#include "content/public/browser/resource_throttle.h" #include "content/public/common/content_features.h" #include "content/public/common/webplugininfo.h" #include "content/public/test/browser_test_utils.h" @@ -118,17 +122,13 @@ static DownloadManagerImpl* DownloadManagerForShell(Shell* shell) { class DownloadFileWithDelay : public DownloadFileImpl { public: - DownloadFileWithDelay( - scoped_ptr<DownloadSaveInfo> save_info, - const base::FilePath& default_download_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, - scoped_ptr<ByteStreamReader> stream, - const net::BoundNetLog& bound_net_log, - scoped_ptr<PowerSaveBlocker> power_save_blocker, - base::WeakPtr<DownloadDestinationObserver> observer, - base::WeakPtr<DownloadFileWithDelayFactory> owner); + DownloadFileWithDelay(scoped_ptr<DownloadSaveInfo> save_info, + const base::FilePath& default_download_directory, + scoped_ptr<ByteStreamReader> stream, + const net::BoundNetLog& bound_net_log, + scoped_ptr<PowerSaveBlocker> power_save_blocker, + base::WeakPtr<DownloadDestinationObserver> observer, + base::WeakPtr<DownloadFileWithDelayFactory> owner); ~DownloadFileWithDelay() override; @@ -138,6 +138,9 @@ class DownloadFileWithDelay : public DownloadFileImpl { void RenameAndUniquify(const base::FilePath& full_path, const RenameCompletionCallback& callback) override; void RenameAndAnnotate(const base::FilePath& full_path, + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url, const RenameCompletionCallback& callback) override; private: @@ -167,9 +170,6 @@ class DownloadFileWithDelayFactory : public DownloadFileFactory { DownloadFile* CreateFile( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_download_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, base::WeakPtr<DownloadDestinationObserver> observer) override; @@ -191,9 +191,6 @@ class DownloadFileWithDelayFactory : public DownloadFileFactory { DownloadFileWithDelay::DownloadFileWithDelay( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_download_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, scoped_ptr<PowerSaveBlocker> power_save_blocker, @@ -201,9 +198,6 @@ DownloadFileWithDelay::DownloadFileWithDelay( base::WeakPtr<DownloadFileWithDelayFactory> owner) : DownloadFileImpl(std::move(save_info), default_download_directory, - url, - referrer_url, - calculate_hash, std::move(stream), bound_net_log, observer), @@ -221,11 +215,19 @@ void DownloadFileWithDelay::RenameAndUniquify( } void DownloadFileWithDelay::RenameAndAnnotate( - const base::FilePath& full_path, const RenameCompletionCallback& callback) { + const base::FilePath& full_path, + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url, + const RenameCompletionCallback& callback) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); DownloadFileImpl::RenameAndAnnotate( - full_path, base::Bind(DownloadFileWithDelay::RenameCallbackWrapper, - owner_, callback)); + full_path, + client_guid, + source_url, + referrer_url, + base::Bind( + DownloadFileWithDelay::RenameCallbackWrapper, owner_, callback)); } // static @@ -249,19 +251,19 @@ DownloadFileWithDelayFactory::~DownloadFileWithDelayFactory() {} DownloadFile* DownloadFileWithDelayFactory::CreateFile( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_download_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, base::WeakPtr<DownloadDestinationObserver> observer) { scoped_ptr<PowerSaveBlocker> psb(PowerSaveBlocker::Create( PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, PowerSaveBlocker::kReasonOther, "Download in progress")); - return new DownloadFileWithDelay( - std::move(save_info), default_download_directory, url, referrer_url, - calculate_hash, std::move(stream), bound_net_log, std::move(psb), - observer, weak_ptr_factory_.GetWeakPtr()); + return new DownloadFileWithDelay(std::move(save_info), + default_download_directory, + std::move(stream), + bound_net_log, + std::move(psb), + observer, + weak_ptr_factory_.GetWeakPtr()); } void DownloadFileWithDelayFactory::AddRenameCallback(base::Closure callback) { @@ -291,18 +293,12 @@ class CountingDownloadFile : public DownloadFileImpl { public: CountingDownloadFile(scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_downloads_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, scoped_ptr<PowerSaveBlocker> power_save_blocker, base::WeakPtr<DownloadDestinationObserver> observer) : DownloadFileImpl(std::move(save_info), default_downloads_directory, - url, - referrer_url, - calculate_hash, std::move(stream), bound_net_log, observer) {} @@ -351,19 +347,18 @@ class CountingDownloadFileFactory : public DownloadFileFactory { DownloadFile* CreateFile( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_downloads_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, base::WeakPtr<DownloadDestinationObserver> observer) override { scoped_ptr<PowerSaveBlocker> psb(PowerSaveBlocker::Create( PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, PowerSaveBlocker::kReasonOther, "Download in progress")); - return new CountingDownloadFile( - std::move(save_info), default_downloads_directory, url, referrer_url, - calculate_hash, std::move(stream), bound_net_log, std::move(psb), - observer); + return new CountingDownloadFile(std::move(save_info), + default_downloads_directory, + std::move(stream), + bound_net_log, + std::move(psb), + observer); } }; @@ -551,6 +546,13 @@ class TestRequestStartHandler { class DownloadContentTest : public ContentBrowserTest { protected: void SetUpOnMainThread() override { + // Enable downloads resumption. + base::FeatureList::ClearInstanceForTesting(); + scoped_ptr<base::FeatureList> feature_list(new base::FeatureList); + feature_list->InitializeFromCommandLine(features::kDownloadResumption.name, + std::string()); + base::FeatureList::SetInstance(std::move(feature_list)); + ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir()); test_delegate_.reset(new TestShellDownloadManagerDelegate()); @@ -575,6 +577,10 @@ class DownloadContentTest : public ContentBrowserTest { return test_delegate_.get(); } + const base::FilePath& GetDownloadDirectory() const { + return downloads_directory_.path(); + } + // Create a DownloadTestObserverTerminal that will wait for the // specified number of downloads to finish. DownloadTestObserver* CreateWaiter( @@ -602,6 +608,12 @@ class DownloadContentTest : public ContentBrowserTest { .WaitForEvent(); } + void WaitForCancel(DownloadItem* download) { + DownloadUpdatedObserver( + download, base::Bind(&IsDownloadInState, DownloadItem::CANCELLED)) + .WaitForEvent(); + } + // Note: Cannot be used with other alternative DownloadFileFactorys void SetupEnsureNoPendingDownloads() { DownloadManagerForShell(shell())->SetDownloadFileFactoryForTesting( @@ -705,71 +717,6 @@ class DownloadContentTest : public ContentBrowserTest { scoped_ptr<TestShellDownloadManagerDelegate> test_delegate_; }; -// Parameters for DownloadResumptionContentTest. -enum class DownloadResumptionTestType { - RESUME_WITH_RENDERER, // Resume() is called while the originating WebContents - // is still alive. - RESUME_WITHOUT_RENDERER // Resume() is called after the originating - // WebContents has been deleted. -}; - -// Parameterized test for download resumption. Tests using this fixure will be -// run once with RESUME_WITH_RENDERER and once with RESUME_WITHOUT_RENDERER. -// Use initiator_shell_for_resumption() to retrieve the Shell object that should -// be used as the originator for the initial download. Prior to calling -// Resume(), call PrepareToResume() which will cause the originating Shell to be -// deleted if the test parameter is RESUME_WITHOUT_RENDERER. -class DownloadResumptionContentTest - : public DownloadContentTest, - public ::testing::WithParamInterface<DownloadResumptionTestType> { - public: - void SetUpOnMainThread() override { - base::FeatureList::ClearInstanceForTesting(); - scoped_ptr<base::FeatureList> feature_list(new base::FeatureList); - feature_list->InitializeFromCommandLine( - features::kDownloadResumption.name, std::string()); - base::FeatureList::SetInstance(std::move(feature_list)); - - DownloadContentTest::SetUpOnMainThread(); - - if (GetParam() == DownloadResumptionTestType::RESUME_WITHOUT_RENDERER) - initiator_shell_for_resumption_ = CreateBrowser(); - else - initiator_shell_for_resumption_ = shell(); - - ASSERT_EQ(DownloadManagerForShell(shell()), - DownloadManagerForShell(initiator_shell_for_resumption())); - } - - // Shell to use for initiating a download. Only valid *before* - // PrepareToResume() is called. - Shell* initiator_shell_for_resumption() const { - DCHECK(initiator_shell_for_resumption_); - return initiator_shell_for_resumption_; - } - - // Should be called once before calling DownloadItem::Resume() on an - // interrupted download. This may cause initiator_shell_for_resumption() to - // become invalidated. - void PrepareToResume() { - if (GetParam() == DownloadResumptionTestType::RESUME_WITH_RENDERER) - return; - DCHECK_NE(initiator_shell_for_resumption(), shell()); - DCHECK(initiator_shell_for_resumption()); - initiator_shell_for_resumption_->Close(); - initiator_shell_for_resumption_ = nullptr; - } - - private: - Shell* initiator_shell_for_resumption_ = nullptr; -}; - -INSTANTIATE_TEST_CASE_P( - _, - DownloadResumptionContentTest, - ::testing::Values(DownloadResumptionTestType::RESUME_WITH_RENDERER, - DownloadResumptionTestType::RESUME_WITHOUT_RENDERER)); - } // namespace IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadCancelled) { @@ -1070,7 +1017,7 @@ IN_PROC_BROWSER_TEST_F(DownloadContentTest, ShutdownAtRelease) { } // Test resumption with a response that contains strong validators. -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, StrongValidators) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, StrongValidators) { TestDownloadRequestHandler request_handler; TestDownloadRequestHandler::Parameters parameters = TestDownloadRequestHandler::Parameters::WithSingleInterruption(); @@ -1078,14 +1025,13 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, StrongValidators) { parameters.injected_errors.front(); request_handler.StartServing(parameters); - DownloadItem* download = StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url()); + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); WaitForInterrupt(download); ASSERT_EQ(interruption.offset, download->GetReceivedBytes()); ASSERT_EQ(parameters.size, download->GetTotalBytes()); - PrepareToResume(); download->Resume(); WaitForCompletion(download); @@ -1123,13 +1069,200 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, StrongValidators) { value); } +// Resumption should only attempt to contact the final URL if the download has a +// URL chain. +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RedirectBeforeResume) { + TestDownloadRequestHandler request_handler_1( + GURL("http://example.com/first-url")); + request_handler_1.StartServingStaticResponse( + "HTTP/1.1 302 Redirect\r\n" + "Location: http://example.com/second-url\r\n" + "\r\n"); + + TestDownloadRequestHandler request_handler_2( + GURL("http://example.com/second-url")); + request_handler_2.StartServingStaticResponse( + "HTTP/1.1 302 Redirect\r\n" + "Location: http://example.com/third-url\r\n" + "\r\n"); + + TestDownloadRequestHandler request_handler_3( + GURL("http://example.com/third-url")); + request_handler_3.StartServingStaticResponse( + "HTTP/1.1 302 Redirect\r\n" + "Location: http://example.com/download\r\n" + "\r\n"); + + TestDownloadRequestHandler resumable_request_handler( + GURL("http://example.com/download")); + TestDownloadRequestHandler::Parameters parameters = + TestDownloadRequestHandler::Parameters::WithSingleInterruption(); + resumable_request_handler.StartServing(parameters); + + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler_1.url()); + WaitForInterrupt(download); + + EXPECT_EQ(4u, download->GetUrlChain().size()); + EXPECT_EQ(request_handler_1.url(), download->GetOriginalUrl()); + EXPECT_EQ(resumable_request_handler.url(), download->GetURL()); + + // Now that the download is interrupted, make all intermediate servers return + // a 404. The only way a resumption request would succeed if the resumption + // request is sent to the final server in the chain. + const char k404Response[] = "HTTP/1.1 404 Not found\r\n\r\n"; + request_handler_1.StartServingStaticResponse(k404Response); + request_handler_2.StartServingStaticResponse(k404Response); + request_handler_3.StartServingStaticResponse(k404Response); + + download->Resume(); + WaitForCompletion(download); + + ASSERT_NO_FATAL_FAILURE(ReadAndVerifyFileContents( + parameters.pattern_generator_seed, parameters.size, + download->GetTargetFilePath())); +} + +// If a resumption request results in a redirect, the response should be ignored +// and the download should be marked as interrupted again. +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RedirectWhileResume) { + TestDownloadRequestHandler request_handler( + GURL("http://example.com/first-url")); + TestDownloadRequestHandler::Parameters parameters = + TestDownloadRequestHandler::Parameters::WithSingleInterruption(); + ++parameters.pattern_generator_seed; + request_handler.StartServing(parameters); + + // We should never send a request to the decoy. If we do, the request will + // always succeed, which results in behavior that diverges from what we want, + // which is for the download to return to being interrupted. + TestDownloadRequestHandler decoy_request_handler( + GURL("http://example.com/decoy")); + decoy_request_handler.StartServing(TestDownloadRequestHandler::Parameters()); + + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); + WaitForInterrupt(download); + + // Upon resumption, the server starts responding with a redirect. This + // response should not be accepted. + request_handler.StartServingStaticResponse( + "HTTP/1.1 302 Redirect\r\n" + "Location: http://example.com/decoy\r\n" + "\r\n"); + download->Resume(); + WaitForInterrupt(download); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_SERVER_UNREACHABLE, + download->GetLastReason()); + + // Back to the original request handler. Resumption should now succeed, and + // use the partial data it had prior to the first interruption. + request_handler.StartServing(parameters); + download->Resume(); + WaitForCompletion(download); + + ASSERT_EQ(parameters.size, download->GetReceivedBytes()); + ASSERT_EQ(parameters.size, download->GetTotalBytes()); + ASSERT_NO_FATAL_FAILURE(ReadAndVerifyFileContents( + parameters.pattern_generator_seed, parameters.size, + download->GetTargetFilePath())); + + // Characterization risk: The next portion of the test examines the requests + // that were sent out while downloading our resource. These requests + // correspond to the requests that were generated by the browser and the + // downloads system and may change as implementation details change. + TestDownloadRequestHandler::CompletedRequests requests; + request_handler.GetCompletedRequestInfo(&requests); + + ASSERT_EQ(3u, requests.size()); + + // None of the request should have transferred the entire resource. The + // redirect response shows up as a response with 0 bytes transferred. + EXPECT_GT(parameters.size, requests[0].transferred_byte_count); + EXPECT_EQ(0, requests[1].transferred_byte_count); + EXPECT_GT(parameters.size, requests[2].transferred_byte_count); +} + +// If the server response for the resumption request specifies a bad range (i.e. +// not the range that was requested or an invalid or missing Content-Range +// header), then the download should be marked as interrupted again without +// discarding the partial state. +IN_PROC_BROWSER_TEST_F(DownloadContentTest, BadRangeHeader) { + TestDownloadRequestHandler request_handler; + TestDownloadRequestHandler::Parameters parameters = + TestDownloadRequestHandler::Parameters::WithSingleInterruption(); + request_handler.StartServing(parameters); + + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); + WaitForInterrupt(download); + + // Upon resumption, the server starts responding with a bad range header. + request_handler.StartServingStaticResponse( + "HTTP/1.1 206 Partial Content\r\n" + "Content-Range: bytes 1000000-2000000/3000000\r\n" + "\r\n"); + download->Resume(); + WaitForInterrupt(download); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, + download->GetLastReason()); + + // Or this time, the server sends a response with an invalid Content-Range + // header. + request_handler.StartServingStaticResponse( + "HTTP/1.1 206 Partial Content\r\n" + "Content-Range: ooga-booga-booga-booga\r\n" + "\r\n"); + download->Resume(); + WaitForInterrupt(download); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, + download->GetLastReason()); + + // Or no Content-Range header at all. + request_handler.StartServingStaticResponse( + "HTTP/1.1 206 Partial Content\r\n" + "Some-Headers: ooga-booga-booga-booga\r\n" + "\r\n"); + download->Resume(); + WaitForInterrupt(download); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, + download->GetLastReason()); + + // Back to the original request handler. Resumption should now succeed, and + // use the partial data it had prior to the first interruption. + request_handler.StartServing(parameters); + download->Resume(); + WaitForCompletion(download); + + ASSERT_EQ(parameters.size, download->GetReceivedBytes()); + ASSERT_EQ(parameters.size, download->GetTotalBytes()); + ASSERT_NO_FATAL_FAILURE(ReadAndVerifyFileContents( + parameters.pattern_generator_seed, parameters.size, + download->GetTargetFilePath())); + + // Characterization risk: The next portion of the test examines the requests + // that were sent out while downloading our resource. These requests + // correspond to the requests that were generated by the browser and the + // downloads system and may change as implementation details change. + TestDownloadRequestHandler::CompletedRequests requests; + request_handler.GetCompletedRequestInfo(&requests); + + ASSERT_EQ(5u, requests.size()); + + // None of the request should have transferred the entire resource. + EXPECT_GT(parameters.size, requests[0].transferred_byte_count); + EXPECT_EQ(0, requests[1].transferred_byte_count); + EXPECT_EQ(0, requests[2].transferred_byte_count); + EXPECT_EQ(0, requests[3].transferred_byte_count); + EXPECT_GT(parameters.size, requests[4].transferred_byte_count); +} + // A partial resumption results in an HTTP 200 response. I.e. the server ignored // the range request and sent the entire resource instead. For If-Range requests // (as opposed to If-Match), the behavior for a precondition failure is also to // respond with a 200. So this test case covers both validation failure and // ignoring the range request. -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, - RestartIfNotPartialResponse) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RestartIfNotPartialResponse) { const int kOriginalPatternGeneratorSeed = 1; const int kNewPatternGeneratorSeed = 2; @@ -1142,8 +1275,8 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, TestDownloadRequestHandler request_handler; request_handler.StartServing(parameters); - DownloadItem* download = StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url()); + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); WaitForInterrupt(download); ASSERT_EQ(interruption.offset, download->GetReceivedBytes()); @@ -1154,7 +1287,6 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, parameters.pattern_generator_seed = kNewPatternGeneratorSeed; request_handler.StartServing(parameters); - PrepareToResume(); download->Resume(); WaitForCompletion(download); @@ -1192,7 +1324,7 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, } // Confirm we restart if we don't have a verifier. -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RestartIfNoETag) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RestartIfNoETag) { const int kOriginalPatternGeneratorSeed = 1; const int kNewPatternGeneratorSeed = 2; @@ -1204,15 +1336,14 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RestartIfNoETag) { TestDownloadRequestHandler request_handler; request_handler.StartServing(parameters); - DownloadItem* download = StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url()); + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); WaitForInterrupt(download); parameters.pattern_generator_seed = kNewPatternGeneratorSeed; parameters.ClearInjectedErrors(); request_handler.StartServing(parameters); - PrepareToResume(); download->Resume(); WaitForCompletion(download); @@ -1236,14 +1367,14 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RestartIfNoETag) { // Partial file goes missing before the download is resumed. The download should // restart. -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RestartIfNoPartialFile) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RestartIfNoPartialFile) { TestDownloadRequestHandler::Parameters parameters = TestDownloadRequestHandler::Parameters::WithSingleInterruption(); TestDownloadRequestHandler request_handler; request_handler.StartServing(parameters); - DownloadItem* download = StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url()); + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); WaitForInterrupt(download); // Delete the intermediate file. @@ -1253,7 +1384,6 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RestartIfNoPartialFile) { parameters.ClearInjectedErrors(); request_handler.StartServing(parameters); - PrepareToResume(); download->Resume(); WaitForCompletion(download); @@ -1264,32 +1394,29 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RestartIfNoPartialFile) { download->GetTargetFilePath())); } -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, - RecoverFromInitFileError) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RecoverFromInitFileError) { TestDownloadRequestHandler request_handler; request_handler.StartServing(TestDownloadRequestHandler::Parameters()); // Setup the error injector. - scoped_refptr<TestFileErrorInjector> injector(TestFileErrorInjector::Create( - DownloadManagerForShell(initiator_shell_for_resumption()))); + scoped_refptr<TestFileErrorInjector> injector( + TestFileErrorInjector::Create(DownloadManagerForShell(shell()))); const TestFileErrorInjector::FileErrorInfo err = { - request_handler.url().spec(), TestFileErrorInjector::FILE_OPERATION_INITIALIZE, 0, DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE}; - injector->AddError(err); - injector->InjectErrors(); + injector->InjectError(err); // Start and watch for interrupt. - DownloadItem* download(StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url())); + DownloadItem* download( + StartDownloadAndReturnItem(shell(), request_handler.url())); WaitForInterrupt(download); ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState()); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE, download->GetLastReason()); EXPECT_EQ(0, download->GetReceivedBytes()); EXPECT_TRUE(download->GetFullPath().empty()); - EXPECT_TRUE(download->GetTargetFilePath().empty()); + EXPECT_FALSE(download->GetTargetFilePath().empty()); // We need to make sure that any cross-thread downloads communication has // quiesced before clearing and injecting the new errors, as the @@ -1299,35 +1426,31 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RunAllPendingInMessageLoop(); // Clear the old errors list. - injector->ClearErrors(); - injector->InjectErrors(); + injector->ClearError(); // Resume and watch completion. - PrepareToResume(); download->Resume(); WaitForCompletion(download); EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE); } -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RecoverFromIntermediateFileRenameError) { TestDownloadRequestHandler request_handler; request_handler.StartServing(TestDownloadRequestHandler::Parameters()); // Setup the error injector. - scoped_refptr<TestFileErrorInjector> injector(TestFileErrorInjector::Create( - DownloadManagerForShell(initiator_shell_for_resumption()))); + scoped_refptr<TestFileErrorInjector> injector( + TestFileErrorInjector::Create(DownloadManagerForShell(shell()))); const TestFileErrorInjector::FileErrorInfo err = { - request_handler.url().spec(), TestFileErrorInjector::FILE_OPERATION_RENAME_UNIQUIFY, 0, DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE}; - injector->AddError(err); - injector->InjectErrors(); + injector->InjectError(err); // Start and watch for interrupt. - DownloadItem* download(StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url())); + DownloadItem* download( + StartDownloadAndReturnItem(shell(), request_handler.url())); WaitForInterrupt(download); ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState()); EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE, @@ -1346,40 +1469,33 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RunAllPendingInMessageLoop(); // Clear the old errors list. - injector->ClearErrors(); - injector->InjectErrors(); + injector->ClearError(); - PrepareToResume(); download->Resume(); WaitForCompletion(download); EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE); } -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, - RecoverFromFinalRenameError) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RecoverFromFinalRenameError) { TestDownloadRequestHandler request_handler; request_handler.StartServing(TestDownloadRequestHandler::Parameters()); // Setup the error injector. - scoped_refptr<TestFileErrorInjector> injector(TestFileErrorInjector::Create( - DownloadManagerForShell(initiator_shell_for_resumption()))); + scoped_refptr<TestFileErrorInjector> injector( + TestFileErrorInjector::Create(DownloadManagerForShell(shell()))); - DownloadManagerForShell(initiator_shell_for_resumption()) - ->RemoveAllDownloads(); + DownloadManagerForShell(shell())->RemoveAllDownloads(); TestFileErrorInjector::FileErrorInfo err = { - request_handler.url().spec(), TestFileErrorInjector::FILE_OPERATION_RENAME_ANNOTATE, 0, - DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE}; - injector->AddError(err); - injector->InjectErrors(); + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED}; + injector->InjectError(err); // Start and watch for interrupt. - DownloadItem* download(StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url())); + DownloadItem* download( + StartDownloadAndReturnItem(shell(), request_handler.url())); WaitForInterrupt(download); ASSERT_EQ(DownloadItem::INTERRUPTED, download->GetState()); - EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE, - download->GetLastReason()); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, download->GetLastReason()); EXPECT_TRUE(download->GetFullPath().empty()); // Target path should still be intact. EXPECT_FALSE(download->GetTargetFilePath().empty()); @@ -1392,16 +1508,14 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RunAllPendingInMessageLoop(); // Clear the old errors list. - injector->ClearErrors(); - injector->InjectErrors(); + injector->ClearError(); - PrepareToResume(); download->Resume(); WaitForCompletion(download); EXPECT_EQ(download->GetState(), DownloadItem::COMPLETE); } -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, Resume_Hash) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, Resume_Hash) { using InjectedError = TestDownloadRequestHandler::InjectedError; const char kExpectedHash[] = "\xa7\x44\x49\x86\x24\xc6\x84\x6c\x89\xdf\xd8\xec\xa0\xe0\x61\x12\xdc\x80" @@ -1412,8 +1526,8 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, Resume_Hash) { // As a control, let's try GetHash() on an uninterrupted download. request_handler.StartServing(parameters); - DownloadItem* uninterrupted_download(StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url())); + DownloadItem* uninterrupted_download( + StartDownloadAndReturnItem(shell(), request_handler.url())); WaitForCompletion(uninterrupted_download); EXPECT_EQ(expected_hash, uninterrupted_download->GetHash()); @@ -1431,11 +1545,10 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, Resume_Hash) { request_handler.StartServing(parameters); // Start and watch for interrupt. - DownloadItem* download(StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url())); + DownloadItem* download( + StartDownloadAndReturnItem(shell(), request_handler.url())); WaitForInterrupt(download); - PrepareToResume(); download->Resume(); WaitForInterrupt(download); @@ -1456,14 +1569,13 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, Resume_Hash) { // An interrupted download should remove the intermediate file when it is // cancelled. -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, - CancelInterruptedDownload) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelInterruptedDownload) { TestDownloadRequestHandler request_handler; request_handler.StartServing( TestDownloadRequestHandler::Parameters::WithSingleInterruption()); - DownloadItem* download = StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url()); + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); WaitForInterrupt(download); base::FilePath intermediate_path = download->GetFullPath(); @@ -1479,14 +1591,13 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, EXPECT_TRUE(download->GetFullPath().empty()); } -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, - RemoveInterruptedDownload) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveInterruptedDownload) { TestDownloadRequestHandler request_handler; request_handler.StartServing( TestDownloadRequestHandler::Parameters::WithSingleInterruption()); - DownloadItem* download = StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url()); + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); WaitForInterrupt(download); base::FilePath intermediate_path = download->GetFullPath(); @@ -1523,14 +1634,14 @@ IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveCompletedDownload) { EXPECT_TRUE(base::PathExists(target_path)); } -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RemoveResumingDownload) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveResumingDownload) { TestDownloadRequestHandler::Parameters parameters = TestDownloadRequestHandler::Parameters::WithSingleInterruption(); TestDownloadRequestHandler request_handler; request_handler.StartServing(parameters); - DownloadItem* download = StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url()); + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); WaitForInterrupt(download); base::FilePath intermediate_path(download->GetFullPath()); @@ -1539,15 +1650,13 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RemoveResumingDownload) { // Resume and remove download. We expect only a single OnDownloadCreated() // call, and that's for the second download created below. - MockDownloadManagerObserver dm_observer( - DownloadManagerForShell(initiator_shell_for_resumption())); + MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell())); EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1); TestRequestStartHandler request_start_handler; parameters.on_start_handler = request_start_handler.GetOnStartHandler(); request_handler.StartServing(parameters); - PrepareToResume(); download->Resume(); request_start_handler.WaitForCallback(); @@ -1575,14 +1684,14 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RemoveResumingDownload) { EXPECT_TRUE(EnsureNoPendingDownloads()); } -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, CancelResumingDownload) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelResumingDownload) { TestDownloadRequestHandler::Parameters parameters = TestDownloadRequestHandler::Parameters::WithSingleInterruption(); TestDownloadRequestHandler request_handler; request_handler.StartServing(parameters); - DownloadItem* download = StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url()); + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); WaitForInterrupt(download); base::FilePath intermediate_path(download->GetFullPath()); @@ -1591,15 +1700,13 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, CancelResumingDownload) { // Resume and cancel download. We expect only a single OnDownloadCreated() // call, and that's for the second download created below. - MockDownloadManagerObserver dm_observer( - DownloadManagerForShell(initiator_shell_for_resumption())); + MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell())); EXPECT_CALL(dm_observer, OnDownloadCreated(_,_)).Times(1); TestRequestStartHandler request_start_handler; parameters.on_start_handler = request_start_handler.GetOnStartHandler(); request_handler.StartServing(parameters); - PrepareToResume(); download->Resume(); request_start_handler.WaitForCallback(); @@ -1628,14 +1735,14 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, CancelResumingDownload) { EXPECT_TRUE(EnsureNoPendingDownloads()); } -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RemoveResumedDownload) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, RemoveResumedDownload) { TestDownloadRequestHandler::Parameters parameters = TestDownloadRequestHandler::Parameters::WithSingleInterruption(); TestDownloadRequestHandler request_handler; request_handler.StartServing(parameters); - DownloadItem* download = StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url()); + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); WaitForInterrupt(download); base::FilePath intermediate_path(download->GetFullPath()); @@ -1645,11 +1752,9 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RemoveResumedDownload) { EXPECT_FALSE(base::PathExists(target_path)); // Resume and remove download. We don't expect OnDownloadCreated() calls. - MockDownloadManagerObserver dm_observer( - DownloadManagerForShell(initiator_shell_for_resumption())); + MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell())); EXPECT_CALL(dm_observer, OnDownloadCreated(_, _)).Times(0); - PrepareToResume(); download->Resume(); WaitForInProgress(download); @@ -1663,14 +1768,14 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, RemoveResumedDownload) { EXPECT_TRUE(EnsureNoPendingDownloads()); } -IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, CancelResumedDownload) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelResumedDownload) { TestDownloadRequestHandler::Parameters parameters = TestDownloadRequestHandler::Parameters::WithSingleInterruption(); TestDownloadRequestHandler request_handler; request_handler.StartServing(parameters); - DownloadItem* download = StartDownloadAndReturnItem( - initiator_shell_for_resumption(), request_handler.url()); + DownloadItem* download = + StartDownloadAndReturnItem(shell(), request_handler.url()); WaitForInterrupt(download); base::FilePath intermediate_path(download->GetFullPath()); @@ -1680,11 +1785,9 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, CancelResumedDownload) { EXPECT_FALSE(base::PathExists(target_path)); // Resume and remove download. We don't expect OnDownloadCreated() calls. - MockDownloadManagerObserver dm_observer( - DownloadManagerForShell(initiator_shell_for_resumption())); + MockDownloadManagerObserver dm_observer(DownloadManagerForShell(shell())); EXPECT_CALL(dm_observer, OnDownloadCreated(_, _)).Times(0); - PrepareToResume(); download->Resume(); WaitForInProgress(download); @@ -1698,6 +1801,415 @@ IN_PROC_BROWSER_TEST_P(DownloadResumptionContentTest, CancelResumedDownload) { EXPECT_TRUE(EnsureNoPendingDownloads()); } +IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeRestoredDownload_NoFile) { + TestDownloadRequestHandler request_handler; + TestDownloadRequestHandler::Parameters parameters; + request_handler.StartServing(parameters); + + base::FilePath intermediate_file_path = + GetDownloadDirectory().AppendASCII("intermediate"); + std::vector<GURL> url_chain; + + const int kIntermediateSize = 1331; + url_chain.push_back(request_handler.url()); + + DownloadItem* download = DownloadManagerForShell(shell())->CreateDownloadItem( + "F7FB1F59-7DE1-4845-AFDB-8A688F70F583", + 1, + intermediate_file_path, + base::FilePath(), + url_chain, + GURL(), + "application/octet-stream", + "application/octet-stream", + base::Time::Now(), + base::Time(), + parameters.etag, + std::string(), + kIntermediateSize, + parameters.size, + std::string(), + DownloadItem::INTERRUPTED, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, + false); + + download->Resume(); + WaitForCompletion(download); + + EXPECT_FALSE(base::PathExists(intermediate_file_path)); + ReadAndVerifyFileContents(parameters.pattern_generator_seed, + parameters.size, + download->GetTargetFilePath()); + + TestDownloadRequestHandler::CompletedRequests completed_requests; + request_handler.GetCompletedRequestInfo(&completed_requests); + + // There will be two requests. The first one is issued optimistically assuming + // that the intermediate file exists and matches the size expectations set + // forth in the download metadata (i.e. assuming that a 1331 byte file exists + // at |intermediate_file_path|. + // + // However, once the response is received, DownloadFile will report that the + // intermediate file doesn't exist and hence the download is marked + // interrupted again. + // + // The second request reads the entire entity. + // + // N.b. we can't make any assumptions about how many bytes are transferred by + // the first request since response data will be bufferred until DownloadFile + // is done initializing. + // + // TODO(asanka): Ideally we'll check that the intermediate file matches + // expectations prior to issuing the first resumption request. + ASSERT_EQ(2u, completed_requests.size()); + EXPECT_EQ(parameters.size, completed_requests[1].transferred_byte_count); +} + +IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeRestoredDownload_NoHash) { + TestDownloadRequestHandler request_handler; + TestDownloadRequestHandler::Parameters parameters; + request_handler.StartServing(parameters); + + base::FilePath intermediate_file_path = + GetDownloadDirectory().AppendASCII("intermediate"); + std::vector<GURL> url_chain; + + const int kIntermediateSize = 1331; + std::vector<char> buffer(kIntermediateSize); + request_handler.GetPatternBytes( + parameters.pattern_generator_seed, 0, buffer.size(), buffer.data()); + ASSERT_EQ( + kIntermediateSize, + base::WriteFile(intermediate_file_path, buffer.data(), buffer.size())); + + url_chain.push_back(request_handler.url()); + + DownloadItem* download = DownloadManagerForShell(shell())->CreateDownloadItem( + "F7FB1F59-7DE1-4845-AFDB-8A688F70F583", + 1, + intermediate_file_path, + base::FilePath(), + url_chain, + GURL(), + "application/octet-stream", + "application/octet-stream", + base::Time::Now(), + base::Time(), + parameters.etag, + std::string(), + kIntermediateSize, + parameters.size, + std::string(), + DownloadItem::INTERRUPTED, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, + false); + + download->Resume(); + WaitForCompletion(download); + + EXPECT_FALSE(base::PathExists(intermediate_file_path)); + ReadAndVerifyFileContents(parameters.pattern_generator_seed, + parameters.size, + download->GetTargetFilePath()); + + TestDownloadRequestHandler::CompletedRequests completed_requests; + request_handler.GetCompletedRequestInfo(&completed_requests); + + // There's only one network request issued, and that is for the remainder of + // the file. + ASSERT_EQ(1u, completed_requests.size()); + EXPECT_EQ(parameters.size - kIntermediateSize, + completed_requests[0].transferred_byte_count); +} + +IN_PROC_BROWSER_TEST_F(DownloadContentTest, + ResumeRestoredDownload_EtagMismatch) { + TestDownloadRequestHandler request_handler; + TestDownloadRequestHandler::Parameters parameters; + request_handler.StartServing(parameters); + + base::FilePath intermediate_file_path = + GetDownloadDirectory().AppendASCII("intermediate"); + std::vector<GURL> url_chain; + + const int kIntermediateSize = 1331; + std::vector<char> buffer(kIntermediateSize); + request_handler.GetPatternBytes( + parameters.pattern_generator_seed + 1, 0, buffer.size(), buffer.data()); + ASSERT_EQ( + kIntermediateSize, + base::WriteFile(intermediate_file_path, buffer.data(), buffer.size())); + + url_chain.push_back(request_handler.url()); + + DownloadItem* download = DownloadManagerForShell(shell())->CreateDownloadItem( + "F7FB1F59-7DE1-4845-AFDB-8A688F70F583", + 1, + intermediate_file_path, + base::FilePath(), + url_chain, + GURL(), + "application/octet-stream", + "application/octet-stream", + base::Time::Now(), + base::Time(), + "fake-etag", + std::string(), + kIntermediateSize, + parameters.size, + std::string(), + DownloadItem::INTERRUPTED, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, + false); + + download->Resume(); + WaitForCompletion(download); + + EXPECT_FALSE(base::PathExists(intermediate_file_path)); + ReadAndVerifyFileContents(parameters.pattern_generator_seed, + parameters.size, + download->GetTargetFilePath()); + + TestDownloadRequestHandler::CompletedRequests completed_requests; + request_handler.GetCompletedRequestInfo(&completed_requests); + + // There's only one network request issued. The If-Range header allows the + // server to respond with the entire entity in one go. The existing contents + // of the file should be discarded, and overwritten by the new contents. + ASSERT_EQ(1u, completed_requests.size()); + EXPECT_EQ(parameters.size, completed_requests[0].transferred_byte_count); +} + +IN_PROC_BROWSER_TEST_F(DownloadContentTest, + ResumeRestoredDownload_CorrectHash) { + TestDownloadRequestHandler request_handler; + TestDownloadRequestHandler::Parameters parameters; + request_handler.StartServing(parameters); + + base::FilePath intermediate_file_path = + GetDownloadDirectory().AppendASCII("intermediate"); + std::vector<GURL> url_chain; + + const int kIntermediateSize = 1331; + std::vector<char> buffer(kIntermediateSize); + request_handler.GetPatternBytes( + parameters.pattern_generator_seed, 0, buffer.size(), buffer.data()); + ASSERT_EQ( + kIntermediateSize, + base::WriteFile(intermediate_file_path, buffer.data(), buffer.size())); + // SHA-256 hash of the pattern bytes in buffer. + static const uint8_t kPartialHash[] = { + 0x77, 0x14, 0xfd, 0x83, 0x06, 0x15, 0x10, 0x7a, 0x47, 0x15, 0xd3, + 0xcf, 0xdd, 0x46, 0xa2, 0x61, 0x96, 0xff, 0xc3, 0xbb, 0x49, 0x30, + 0xaf, 0x31, 0x3a, 0x64, 0x0b, 0xd5, 0xfa, 0xb1, 0xe3, 0x81}; + + url_chain.push_back(request_handler.url()); + + DownloadItem* download = DownloadManagerForShell(shell())->CreateDownloadItem( + "F7FB1F59-7DE1-4845-AFDB-8A688F70F583", + 1, + intermediate_file_path, + base::FilePath(), + url_chain, + GURL(), + "application/octet-stream", + "application/octet-stream", + base::Time::Now(), + base::Time(), + parameters.etag, + std::string(), + kIntermediateSize, + parameters.size, + std::string(std::begin(kPartialHash), std::end(kPartialHash)), + DownloadItem::INTERRUPTED, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, + false); + + download->Resume(); + WaitForCompletion(download); + + EXPECT_FALSE(base::PathExists(intermediate_file_path)); + ReadAndVerifyFileContents(parameters.pattern_generator_seed, + parameters.size, + download->GetTargetFilePath()); + + TestDownloadRequestHandler::CompletedRequests completed_requests; + request_handler.GetCompletedRequestInfo(&completed_requests); + + // There's only one network request issued, and that is for the remainder of + // the file. + ASSERT_EQ(1u, completed_requests.size()); + EXPECT_EQ(parameters.size - kIntermediateSize, + completed_requests[0].transferred_byte_count); + + // SHA-256 hash of the entire 102400 bytes in the target file. + static const uint8_t kFullHash[] = { + 0xa7, 0x44, 0x49, 0x86, 0x24, 0xc6, 0x84, 0x6c, 0x89, 0xdf, 0xd8, + 0xec, 0xa0, 0xe0, 0x61, 0x12, 0xdc, 0x80, 0x13, 0xf2, 0x83, 0x49, + 0xa9, 0x14, 0x52, 0x32, 0xf0, 0x95, 0x20, 0xca, 0x5b, 0x30}; + EXPECT_EQ(std::string(std::begin(kFullHash), std::end(kFullHash)), + download->GetHash()); +} + +IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeRestoredDownload_WrongHash) { + TestDownloadRequestHandler request_handler; + TestDownloadRequestHandler::Parameters parameters; + request_handler.StartServing(parameters); + + base::FilePath intermediate_file_path = + GetDownloadDirectory().AppendASCII("intermediate"); + std::vector<GURL> url_chain; + + const int kIntermediateSize = 1331; + std::vector<char> buffer(kIntermediateSize); + ASSERT_EQ( + kIntermediateSize, + base::WriteFile(intermediate_file_path, buffer.data(), buffer.size())); + // SHA-256 hash of the expected pattern bytes in buffer. This doesn't match + // the current contents of the intermediate file which should all be 0. + static const uint8_t kPartialHash[] = { + 0x77, 0x14, 0xfd, 0x83, 0x06, 0x15, 0x10, 0x7a, 0x47, 0x15, 0xd3, + 0xcf, 0xdd, 0x46, 0xa2, 0x61, 0x96, 0xff, 0xc3, 0xbb, 0x49, 0x30, + 0xaf, 0x31, 0x3a, 0x64, 0x0b, 0xd5, 0xfa, 0xb1, 0xe3, 0x81}; + + url_chain.push_back(request_handler.url()); + + DownloadItem* download = DownloadManagerForShell(shell())->CreateDownloadItem( + "F7FB1F59-7DE1-4845-AFDB-8A688F70F583", + 1, + intermediate_file_path, + base::FilePath(), + url_chain, + GURL(), + "application/octet-stream", + "application/octet-stream", + base::Time::Now(), + base::Time(), + parameters.etag, + std::string(), + kIntermediateSize, + parameters.size, + std::string(std::begin(kPartialHash), std::end(kPartialHash)), + DownloadItem::INTERRUPTED, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, + false); + + download->Resume(); + WaitForCompletion(download); + + EXPECT_FALSE(base::PathExists(intermediate_file_path)); + ReadAndVerifyFileContents(parameters.pattern_generator_seed, + parameters.size, + download->GetTargetFilePath()); + + TestDownloadRequestHandler::CompletedRequests completed_requests; + request_handler.GetCompletedRequestInfo(&completed_requests); + + // There will be two requests. The first one is issued optimistically assuming + // that the intermediate file exists and matches the size expectations set + // forth in the download metadata (i.e. assuming that a 1331 byte file exists + // at |intermediate_file_path|. + // + // However, once the response is received, DownloadFile will report that the + // intermediate file doesn't match the expected hash. + // + // The second request reads the entire entity. + // + // N.b. we can't make any assumptions about how many bytes are transferred by + // the first request since response data will be bufferred until DownloadFile + // is done initializing. + // + // TODO(asanka): Ideally we'll check that the intermediate file matches + // expectations prior to issuing the first resumption request. + ASSERT_EQ(2u, completed_requests.size()); + EXPECT_EQ(parameters.size, completed_requests[1].transferred_byte_count); + + // SHA-256 hash of the entire 102400 bytes in the target file. + static const uint8_t kFullHash[] = { + 0xa7, 0x44, 0x49, 0x86, 0x24, 0xc6, 0x84, 0x6c, 0x89, 0xdf, 0xd8, + 0xec, 0xa0, 0xe0, 0x61, 0x12, 0xdc, 0x80, 0x13, 0xf2, 0x83, 0x49, + 0xa9, 0x14, 0x52, 0x32, 0xf0, 0x95, 0x20, 0xca, 0x5b, 0x30}; + EXPECT_EQ(std::string(std::begin(kFullHash), std::end(kFullHash)), + download->GetHash()); +} + +IN_PROC_BROWSER_TEST_F(DownloadContentTest, ResumeRestoredDownload_ShortFile) { + TestDownloadRequestHandler request_handler; + TestDownloadRequestHandler::Parameters parameters; + request_handler.StartServing(parameters); + + base::FilePath intermediate_file_path = + GetDownloadDirectory().AppendASCII("intermediate"); + std::vector<GURL> url_chain; + + const int kIntermediateSize = 1331; + // Size of file is slightly shorter than the size known to DownloadItem. + std::vector<char> buffer(kIntermediateSize - 100); + request_handler.GetPatternBytes( + parameters.pattern_generator_seed, 0, buffer.size(), buffer.data()); + ASSERT_EQ( + kIntermediateSize - 100, + base::WriteFile(intermediate_file_path, buffer.data(), buffer.size())); + url_chain.push_back(request_handler.url()); + + DownloadItem* download = DownloadManagerForShell(shell())->CreateDownloadItem( + "F7FB1F59-7DE1-4845-AFDB-8A688F70F583", + 1, + intermediate_file_path, + base::FilePath(), + url_chain, + GURL(), + "application/octet-stream", + "application/octet-stream", + base::Time::Now(), + base::Time(), + parameters.etag, + std::string(), + kIntermediateSize, + parameters.size, + std::string(), + DownloadItem::INTERRUPTED, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, + false); + + download->Resume(); + WaitForCompletion(download); + + EXPECT_FALSE(base::PathExists(intermediate_file_path)); + ReadAndVerifyFileContents(parameters.pattern_generator_seed, + parameters.size, + download->GetTargetFilePath()); + + TestDownloadRequestHandler::CompletedRequests completed_requests; + request_handler.GetCompletedRequestInfo(&completed_requests); + + // There will be two requests. The first one is issued optimistically assuming + // that the intermediate file exists and matches the size expectations set + // forth in the download metadata (i.e. assuming that a 1331 byte file exists + // at |intermediate_file_path|. + // + // However, once the response is received, DownloadFile will report that the + // intermediate file is too short and hence the download is marked interrupted + // again. + // + // The second request reads the entire entity. + // + // N.b. we can't make any assumptions about how many bytes are transferred by + // the first request since response data will be bufferred until DownloadFile + // is done initializing. + // + // TODO(asanka): Ideally we'll check that the intermediate file matches + // expectations prior to issuing the first resumption request. + ASSERT_EQ(2u, completed_requests.size()); + EXPECT_EQ(parameters.size, completed_requests[1].transferred_byte_count); +} + // Check that the cookie policy is correctly updated when downloading a file // that redirects cross origin. IN_PROC_BROWSER_TEST_F(DownloadContentTest, CookiePolicy) { @@ -1834,6 +2346,81 @@ IN_PROC_BROWSER_TEST_F(DownloadContentTest, ASSERT_TRUE(origin_two.ShutdownAndWaitUntilComplete()); } +// A request for a non-existent resource should still result in a DownloadItem +// that's created in an interrupted state. +IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadAttributeServerError) { + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL download_url = + embedded_test_server()->GetURL("/download/does-not-exist"); + GURL document_url = embedded_test_server()->GetURL( + std::string("/download/download-attribute.html?target=") + + download_url.spec()); + + DownloadItem* download = StartDownloadAndReturnItem(shell(), document_url); + WaitForInterrupt(download); + + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT, + download->GetLastReason()); +} + +namespace { + +void ErrorReturningRequestHandler( + const net::HttpRequestHeaders& headers, + const TestDownloadRequestHandler::OnStartResponseCallback& callback) { + callback.Run(std::string(), net::ERR_INTERNET_DISCONNECTED); +} + +} // namespace + +// A request that fails before it gets a response from the server should also +// result in a DownloadItem that's created in an interrupted state. +IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadAttributeNetworkError) { + ASSERT_TRUE(embedded_test_server()->Start()); + TestDownloadRequestHandler request_handler; + TestDownloadRequestHandler::Parameters parameters; + + parameters.on_start_handler = base::Bind(&ErrorReturningRequestHandler); + request_handler.StartServing(parameters); + + GURL document_url = embedded_test_server()->GetURL( + std::string("/download/download-attribute.html?target=") + + request_handler.url().spec()); + DownloadItem* download = StartDownloadAndReturnItem(shell(), document_url); + WaitForInterrupt(download); + + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, + download->GetLastReason()); +} + +// A request that fails due to it being rejected by policy should result in a +// DownloadItem that's marked as interrupted. +IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadAttributeInvalidURL) { + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL document_url = embedded_test_server()->GetURL( + "/download/download-attribute.html?target=about:version"); + DownloadItem* download = StartDownloadAndReturnItem(shell(), document_url); + WaitForInterrupt(download); + + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST, + download->GetLastReason()); + EXPECT_FALSE(download->CanResume()); +} + +IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadAttributeBlobURL) { + ASSERT_TRUE(embedded_test_server()->Start()); + + GURL document_url = + embedded_test_server()->GetURL("/download/download-attribute-blob.html"); + DownloadItem* download = StartDownloadAndReturnItem(shell(), document_url); + WaitForCompletion(download); + + EXPECT_STREQ(FILE_PATH_LITERAL("suggested-filename.txt"), + download->GetTargetFilePath().BaseName().value().c_str()); +} + // The file empty.bin is served with a MIME type of application/octet-stream. // The content body is empty. Make sure this case is handled properly and we // don't regress on http://crbug.com/320394. @@ -1854,9 +2441,11 @@ IN_PROC_BROWSER_TEST_F(DownloadContentTest, SniffedMimeType) { EXPECT_TRUE(item->GetOriginalMimeType().empty()); } -IN_PROC_BROWSER_TEST_F(DownloadContentTest, Spam) { +IN_PROC_BROWSER_TEST_F(DownloadContentTest, DuplicateContentDisposition) { ASSERT_TRUE(embedded_test_server()->Start()); + // double-content-disposition.txt is served with two Content-Disposition + // headers, both of which are identical. NavigateToURLAndWaitForDownload( shell(), embedded_test_server()->GetURL( diff --git a/chromium/content/browser/download/download_create_info.cc b/chromium/content/browser/download/download_create_info.cc index c09d713aa97..92b15849b37 100644 --- a/chromium/content/browser/download/download_create_info.cc +++ b/chromium/content/browser/download/download_create_info.cc @@ -5,7 +5,6 @@ #include "content/browser/download/download_create_info.h" #include <string> -#include <utility> #include "base/format_macros.h" #include "base/strings/stringprintf.h" @@ -13,36 +12,24 @@ namespace content { DownloadCreateInfo::DownloadCreateInfo(const base::Time& start_time, - int64_t total_bytes, const net::BoundNetLog& bound_net_log, scoped_ptr<DownloadSaveInfo> save_info) - : start_time(start_time), - total_bytes(total_bytes), - download_id(DownloadItem::kInvalidId), + : download_id(DownloadItem::kInvalidId), + start_time(start_time), + total_bytes(0), has_user_gesture(false), transition_type(ui::PAGE_TRANSITION_LINK), + result(DOWNLOAD_INTERRUPT_REASON_NONE), save_info(std::move(save_info)), request_bound_net_log(bound_net_log) {} DownloadCreateInfo::DownloadCreateInfo() : DownloadCreateInfo(base::Time(), - 0, net::BoundNetLog(), make_scoped_ptr(new DownloadSaveInfo)) {} DownloadCreateInfo::~DownloadCreateInfo() {} -std::string DownloadCreateInfo::DebugString() const { - return base::StringPrintf( - "{" - " download_id = %u" - " url = \"%s\"" - " request_handle = %s" - " total_bytes = %" PRId64 " }", - download_id, url().spec().c_str(), request_handle->DebugString().c_str(), - total_bytes); -} - const GURL& DownloadCreateInfo::url() const { return url_chain.empty() ? GURL::EmptyGURL() : url_chain.back(); } diff --git a/chromium/content/browser/download/download_create_info.h b/chromium/content/browser/download/download_create_info.h index 1767e45eebc..01e1ad5f014 100644 --- a/chromium/content/browser/download/download_create_info.h +++ b/chromium/content/browser/download/download_create_info.h @@ -16,6 +16,7 @@ #include "content/browser/download/download_file.h" #include "content/browser/download/download_request_handle.h" #include "content/common/content_export.h" +#include "content/public/browser/download_interrupt_reasons.h" #include "content/public/browser/download_save_info.h" #include "net/log/net_log.h" #include "ui/base/page_transition_types.h" @@ -27,18 +28,18 @@ namespace content { // want to pass |DownloadItem|s between threads. struct CONTENT_EXPORT DownloadCreateInfo { DownloadCreateInfo(const base::Time& start_time, - int64_t total_bytes, const net::BoundNetLog& bound_net_log, scoped_ptr<DownloadSaveInfo> save_info); DownloadCreateInfo(); ~DownloadCreateInfo(); - std::string DebugString() const; - // The URL from which we are downloading. This is the final URL after any // redirection by the server for |url_chain|. const GURL& url() const; + // The ID of the download. + uint32_t download_id; + // The chain of redirects that leading up to and including the final URL. std::vector<GURL> url_chain; @@ -57,14 +58,35 @@ struct CONTENT_EXPORT DownloadCreateInfo { // The total download size. int64_t total_bytes; - // The ID of the download. - uint32_t download_id; - // True if the download was initiated by user action. bool has_user_gesture; ui::PageTransition transition_type; + // The remote IP address where the download was fetched from. Copied from + // UrlRequest::GetSocketAddress(). + std::string remote_address; + + // If the download is initially created in an interrupted state (because the + // response was in error), then |result| would be something other than + // INTERRUPT_REASON_NONE. + DownloadInterruptReason result; + + // The download file save info. + scoped_ptr<DownloadSaveInfo> save_info; + + // The handle to the URLRequest sourcing this download. + scoped_ptr<DownloadRequestHandleInterface> request_handle; + + // The request's |BoundNetLog|, for "source_dependency" linking with the + // download item's. + const net::BoundNetLog request_bound_net_log; + + // --------------------------------------------------------------------------- + // The remaining fields are Entity-body properties. These are only set if + // |result| is DOWNLOAD_INTERRUPT_REASON_NONE. + // --------------------------------------------------------------------------- + // The content-disposition string from the response header. std::string content_disposition; @@ -81,23 +103,9 @@ struct CONTENT_EXPORT DownloadCreateInfo { // "If-Unmodified-Since" comparison. std::string last_modified; - // For continuing a download, the ETAG of the file. + // For continuing a download, the ETag of the file. std::string etag; - // The download file save info. - scoped_ptr<DownloadSaveInfo> save_info; - - // The remote IP address where the download was fetched from. Copied from - // UrlRequest::GetSocketAddress(). - std::string remote_address; - - // The handle to the URLRequest sourcing this download. - scoped_ptr<DownloadRequestHandleInterface> request_handle; - - // The request's |BoundNetLog|, for "source_dependency" linking with the - // download item's. - const net::BoundNetLog request_bound_net_log; - private: DISALLOW_COPY_AND_ASSIGN(DownloadCreateInfo); }; diff --git a/chromium/content/browser/download/download_destination_observer.h b/chromium/content/browser/download/download_destination_observer.h new file mode 100644 index 00000000000..6f76391ecf9 --- /dev/null +++ b/chromium/content/browser/download/download_destination_observer.h @@ -0,0 +1,44 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_DESTINATION_OBSERVER_H_ +#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_DESTINATION_OBSERVER_H_ + +#include <stdint.h> + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "content/public/browser/download_interrupt_reasons.h" +#include "crypto/secure_hash.h" + +namespace content { + +// Class that receives asynchronous events from a DownloadDestination about +// downloading progress and completion. These should report status when the +// data arrives at its final location; i.e. DestinationUpdate should be +// called after the destination is finished with whatever operation it +// is doing on the data described by |bytes_so_far| and DestinationCompleted +// should only be called once that is true for all data. +// +// All methods are invoked on the UI thread. +// +// Note that this interface does not deal with cross-thread lifetime issues. +class DownloadDestinationObserver { + public: + virtual void DestinationUpdate(int64_t bytes_so_far, + int64_t bytes_per_sec) = 0; + + virtual void DestinationError(DownloadInterruptReason reason, + int64_t bytes_so_far, + scoped_ptr<crypto::SecureHash> hash_state) = 0; + + virtual void DestinationCompleted( + int64_t total_bytes, + scoped_ptr<crypto::SecureHash> hash_state) = 0; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_DESTINATION_OBSERVER_H_ diff --git a/chromium/content/browser/download/download_file.h b/chromium/content/browser/download/download_file.h index 2514df15519..c30cab9fe30 100644 --- a/chromium/content/browser/download/download_file.h +++ b/chromium/content/browser/download/download_file.h @@ -14,6 +14,8 @@ #include "content/common/content_export.h" #include "content/public/browser/download_interrupt_reasons.h" +class GURL; + namespace content { class DownloadManager; @@ -55,6 +57,9 @@ class CONTENT_EXPORT DownloadFile { // "Mark of the Web" information about its source. No uniquification // will be performed. virtual void RenameAndAnnotate(const base::FilePath& full_path, + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url, const RenameCompletionCallback& callback) = 0; // Detach the file so it is not deleted on destruction. @@ -63,30 +68,8 @@ class CONTENT_EXPORT DownloadFile { // Abort the download and automatically close the file. virtual void Cancel() = 0; - virtual base::FilePath FullPath() const = 0; + virtual const base::FilePath& FullPath() const = 0; virtual bool InProgress() const = 0; - virtual int64_t CurrentSpeed() const = 0; - - // Set |hash| with sha256 digest for the file. - // Returns true if digest is successfully calculated. - virtual bool GetHash(std::string* hash) = 0; - - // Returns the current (intermediate) state of the hash as a byte string. - virtual std::string GetHashState() = 0; - - // Set the application GUID to be used to identify the app to the - // system AV function when scanning downloaded files. Should be called - // before RenameAndAnnotate() to take effect. - virtual void SetClientGuid(const std::string& guid) = 0; - - // For testing. Must be called on FILE thread. - // TODO(rdsmith): Replace use of EnsureNoPendingDownloads() - // on the DownloadManager with a test-specific DownloadFileFactory - // which keeps track of the number of DownloadFiles. - static int GetNumberOfDownloadFiles(); - - protected: - static int number_active_objects_; }; } // namespace content diff --git a/chromium/content/browser/download/download_file_factory.cc b/chromium/content/browser/download/download_file_factory.cc index bae88bef34e..55e03469c89 100644 --- a/chromium/content/browser/download/download_file_factory.cc +++ b/chromium/content/browser/download/download_file_factory.cc @@ -15,15 +15,14 @@ DownloadFileFactory::~DownloadFileFactory() {} DownloadFile* DownloadFileFactory::CreateFile( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_downloads_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, - scoped_ptr<ByteStreamReader> stream, + scoped_ptr<ByteStreamReader> byte_stream, const net::BoundNetLog& bound_net_log, base::WeakPtr<DownloadDestinationObserver> observer) { - return new DownloadFileImpl(std::move(save_info), default_downloads_directory, - url, referrer_url, calculate_hash, - std::move(stream), bound_net_log, observer); + return new DownloadFileImpl(std::move(save_info), + default_downloads_directory, + std::move(byte_stream), + bound_net_log, + observer); } } // namespace content diff --git a/chromium/content/browser/download/download_file_factory.h b/chromium/content/browser/download/download_file_factory.h index 2600e3f8455..d29384dde08 100644 --- a/chromium/content/browser/download/download_file_factory.h +++ b/chromium/content/browser/download/download_file_factory.h @@ -5,6 +5,7 @@ #ifndef CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_FACTORY_H_ #define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_FACTORY_H_ +#include "base/files/file.h" #include "base/files/file_path.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" @@ -12,6 +13,10 @@ #include "content/common/content_export.h" #include "url/gurl.h" +namespace crypto { +class SecureHash; +} + namespace net { class BoundNetLog; } @@ -31,10 +36,7 @@ class CONTENT_EXPORT DownloadFileFactory { virtual DownloadFile* CreateFile( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_downloads_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, - scoped_ptr<ByteStreamReader> stream, + scoped_ptr<ByteStreamReader> byte_stream, const net::BoundNetLog& bound_net_log, base::WeakPtr<DownloadDestinationObserver> observer); }; diff --git a/chromium/content/browser/download/download_file_impl.cc b/chromium/content/browser/download/download_file_impl.cc index e8e31cee7be..6378b9c8d3b 100644 --- a/chromium/content/browser/download/download_file_impl.cc +++ b/chromium/content/browser/download/download_file_impl.cc @@ -14,11 +14,13 @@ #include "base/values.h" #include "content/browser/byte_stream.h" #include "content/browser/download/download_create_info.h" +#include "content/browser/download/download_destination_observer.h" #include "content/browser/download/download_interrupt_reasons_impl.h" #include "content/browser/download/download_net_log_parameters.h" #include "content/browser/download/download_stats.h" #include "content/public/browser/browser_thread.h" -#include "content/public/browser/download_destination_observer.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" #include "net/base/io_buffer.h" namespace content { @@ -29,28 +31,19 @@ const int kMaxTimeBlockingFileThreadMs = 1000; // These constants control the default retry behavior for failing renames. Each // retry is performed after a delay that is twice the previous delay. The // initial delay is specified by kInitialRenameRetryDelayMs. -const int kMaxRenameRetries = 3; const int kInitialRenameRetryDelayMs = 200; -int DownloadFile::number_active_objects_ = 0; +// Number of times a failing rename is retried before giving up. +const int kMaxRenameRetries = 3; DownloadFileImpl::DownloadFileImpl( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_download_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, base::WeakPtr<DownloadDestinationObserver> observer) - : file_(save_info->file_path, - url, - referrer_url, - save_info->offset, - calculate_hash, - save_info->hash_state, - std::move(save_info->file), - bound_net_log), + : file_(bound_net_log), + save_info_(std::move(save_info)), default_download_directory_(default_download_directory), stream_reader_(std::move(stream)), bytes_seen_(0), @@ -60,7 +53,6 @@ DownloadFileImpl::DownloadFileImpl( DownloadFileImpl::~DownloadFileImpl() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); - --number_active_objects_; } void DownloadFileImpl::Initialize(const InitializeCallback& callback) { @@ -68,7 +60,12 @@ void DownloadFileImpl::Initialize(const InitializeCallback& callback) { update_timer_.reset(new base::RepeatingTimer()); DownloadInterruptReason result = - file_.Initialize(default_download_directory_); + file_.Initialize(save_info_->file_path, + default_download_directory_, + std::move(save_info_->file), + save_info_->offset, + save_info_->hash_of_partial_file, + std::move(save_info_->hash_state)); if (result != DOWNLOAD_INTERRUPT_REASON_NONE) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); @@ -89,8 +86,6 @@ void DownloadFileImpl::Initialize(const InitializeCallback& callback) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind( callback, DOWNLOAD_INTERRUPT_REASON_NONE)); - - ++number_active_objects_; } DownloadInterruptReason DownloadFileImpl::AppendDataToFile( @@ -109,18 +104,23 @@ DownloadInterruptReason DownloadFileImpl::AppendDataToFile( void DownloadFileImpl::RenameAndUniquify( const base::FilePath& full_path, const RenameCompletionCallback& callback) { - RenameWithRetryInternal( - full_path, UNIQUIFY, kMaxRenameRetries, base::TimeTicks(), callback); + scoped_ptr<RenameParameters> parameters( + new RenameParameters(UNIQUIFY, full_path, callback)); + RenameWithRetryInternal(std::move(parameters)); } void DownloadFileImpl::RenameAndAnnotate( const base::FilePath& full_path, + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url, const RenameCompletionCallback& callback) { - RenameWithRetryInternal(full_path, - ANNOTATE_WITH_SOURCE_INFORMATION, - kMaxRenameRetries, - base::TimeTicks(), - callback); + scoped_ptr<RenameParameters> parameters(new RenameParameters( + ANNOTATE_WITH_SOURCE_INFORMATION, full_path, callback)); + parameters->client_guid = client_guid; + parameters->source_url = source_url; + parameters->referrer_url = referrer_url; + RenameWithRetryInternal(std::move(parameters)); } base::TimeDelta DownloadFileImpl::GetRetryDelayForFailedRename( @@ -139,16 +139,12 @@ bool DownloadFileImpl::ShouldRetryFailedRename(DownloadInterruptReason reason) { } void DownloadFileImpl::RenameWithRetryInternal( - const base::FilePath& full_path, - RenameOption option, - int retries_left, - base::TimeTicks time_of_first_failure, - const RenameCompletionCallback& callback) { + scoped_ptr<RenameParameters> parameters) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); - base::FilePath new_path(full_path); + base::FilePath new_path = parameters->new_path; - if ((option & UNIQUIFY) && full_path != file_.full_path()) { + if ((parameters->option & UNIQUIFY) && new_path != file_.full_path()) { int uniquifier = base::GetUniquePathNumber(new_path, base::FilePath::StringType()); if (uniquifier > 0) @@ -164,36 +160,36 @@ void DownloadFileImpl::RenameWithRetryInternal( // have less assurance that the file at file_.full_path() was the one we were // working with. if (ShouldRetryFailedRename(reason) && file_.in_progress() && - retries_left > 0) { - int attempt_number = kMaxRenameRetries - retries_left; + parameters->retries_left > 0) { + int attempt_number = kMaxRenameRetries - parameters->retries_left; + --parameters->retries_left; + if (parameters->time_of_first_failure.is_null()) + parameters->time_of_first_failure = base::TimeTicks::Now(); BrowserThread::PostDelayedTask( BrowserThread::FILE, FROM_HERE, base::Bind(&DownloadFileImpl::RenameWithRetryInternal, weak_factory_.GetWeakPtr(), - full_path, - option, - --retries_left, - time_of_first_failure.is_null() ? base::TimeTicks::Now() - : time_of_first_failure, - callback), + base::Passed(std::move(parameters))), GetRetryDelayForFailedRename(attempt_number)); return; } - if (!time_of_first_failure.is_null()) + if (!parameters->time_of_first_failure.is_null()) RecordDownloadFileRenameResultAfterRetry( - base::TimeTicks::Now() - time_of_first_failure, reason); + base::TimeTicks::Now() - parameters->time_of_first_failure, reason); if (reason == DOWNLOAD_INTERRUPT_REASON_NONE && - (option & ANNOTATE_WITH_SOURCE_INFORMATION)) { + (parameters->option & ANNOTATE_WITH_SOURCE_INFORMATION)) { // Doing the annotation after the rename rather than before leaves // a very small window during which the file has the final name but // hasn't been marked with the Mark Of The Web. However, it allows // anti-virus scanners on Windows to actually see the data // (http://crbug.com/127999) under the correct name (which is information // it uses). - reason = file_.AnnotateWithSourceInformation(); + reason = file_.AnnotateWithSourceInformation(parameters->client_guid, + parameters->source_url, + parameters->referrer_url); } if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) { @@ -208,8 +204,9 @@ void DownloadFileImpl::RenameWithRetryInternal( } BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(callback, reason, new_path)); + BrowserThread::UI, + FROM_HERE, + base::Bind(parameters->completion_callback, reason, new_path)); } void DownloadFileImpl::Detach() { @@ -220,7 +217,7 @@ void DownloadFileImpl::Cancel() { file_.Cancel(); } -base::FilePath DownloadFileImpl::FullPath() const { +const base::FilePath& DownloadFileImpl::FullPath() const { return file_.full_path(); } @@ -228,22 +225,6 @@ bool DownloadFileImpl::InProgress() const { return file_.in_progress(); } -int64_t DownloadFileImpl::CurrentSpeed() const { - return rate_estimator_.GetCountPerSecond(); -} - -bool DownloadFileImpl::GetHash(std::string* hash) { - return file_.GetHash(hash); -} - -std::string DownloadFileImpl::GetHashState() { - return file_.GetHashState(); -} - -void DownloadFileImpl::SetClientGuid(const std::string& guid) { - file_.SetClientGuid(guid); -} - void DownloadFileImpl::StreamActive() { base::TimeTicks start(base::TimeTicks::Now()); base::TimeTicks now; @@ -280,10 +261,6 @@ void DownloadFileImpl::StreamActive() { stream_reader_->GetStatus()); SendUpdate(); base::TimeTicks close_start(base::TimeTicks::Now()); - if (reason == DOWNLOAD_INTERRUPT_REASON_NONE) - file_.Finish(); - else - file_.FinishWithError(); base::TimeTicks now(base::TimeTicks::Now()); disk_writes_time_ += (now - close_start); RecordFileBandwidth( @@ -321,24 +298,29 @@ void DownloadFileImpl::StreamActive() { // Our observer will clean us up. stream_reader_->RegisterCallback(base::Closure()); weak_factory_.InvalidateWeakPtrs(); - SendUpdate(); // Make info up to date before error. + SendUpdate(); // Make info up to date before error. + scoped_ptr<crypto::SecureHash> hash_state = file_.Finish(); BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, + BrowserThread::UI, + FROM_HERE, base::Bind(&DownloadDestinationObserver::DestinationError, - observer_, reason)); + observer_, + reason, + file_.bytes_so_far(), + base::Passed(&hash_state))); } else if (state == ByteStreamReader::STREAM_COMPLETE) { // Signal successful completion and shut down processing. stream_reader_->RegisterCallback(base::Closure()); weak_factory_.InvalidateWeakPtrs(); - std::string hash; - if (!GetHash(&hash) || file_.IsEmptyHash(hash)) - hash.clear(); SendUpdate(); + scoped_ptr<crypto::SecureHash> hash_state = file_.Finish(); BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind( - &DownloadDestinationObserver::DestinationCompleted, - observer_, hash)); + BrowserThread::UI, + FROM_HERE, + base::Bind(&DownloadDestinationObserver::DestinationCompleted, + observer_, + file_.bytes_so_far(), + base::Passed(&hash_state))); } if (bound_net_log_.IsCapturing()) { bound_net_log_.AddEvent( @@ -350,15 +332,23 @@ void DownloadFileImpl::StreamActive() { void DownloadFileImpl::SendUpdate() { BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, + BrowserThread::UI, + FROM_HERE, base::Bind(&DownloadDestinationObserver::DestinationUpdate, - observer_, file_.bytes_so_far(), CurrentSpeed(), - GetHashState())); + observer_, + file_.bytes_so_far(), + rate_estimator_.GetCountPerSecond())); } -// static -int DownloadFile::GetNumberOfDownloadFiles() { - return number_active_objects_; -} +DownloadFileImpl::RenameParameters::RenameParameters( + RenameOption option, + const base::FilePath& new_path, + const RenameCompletionCallback& completion_callback) + : option(option), + new_path(new_path), + retries_left(kMaxRenameRetries), + completion_callback(completion_callback) {} + +DownloadFileImpl::RenameParameters::~RenameParameters() {} } // namespace content diff --git a/chromium/content/browser/download/download_file_impl.h b/chromium/content/browser/download/download_file_impl.h index 98381128505..93b1e551703 100644 --- a/chromium/content/browser/download/download_file_impl.h +++ b/chromium/content/browser/download/download_file_impl.h @@ -10,6 +10,7 @@ #include <stddef.h> #include <stdint.h> +#include "base/files/file.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" @@ -38,15 +39,11 @@ class CONTENT_EXPORT DownloadFileImpl : public DownloadFile { // Note that the DownloadFileImpl automatically reads from the passed in // stream, and sends updates and status of those reads to the // DownloadDestinationObserver. - DownloadFileImpl( - scoped_ptr<DownloadSaveInfo> save_info, - const base::FilePath& default_downloads_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, - scoped_ptr<ByteStreamReader> stream, - const net::BoundNetLog& bound_net_log, - base::WeakPtr<DownloadDestinationObserver> observer); + DownloadFileImpl(scoped_ptr<DownloadSaveInfo> save_info, + const base::FilePath& default_downloads_directory, + scoped_ptr<ByteStreamReader> byte_stream, + const net::BoundNetLog& bound_net_log, + base::WeakPtr<DownloadDestinationObserver> observer); ~DownloadFileImpl() override; @@ -55,15 +52,14 @@ class CONTENT_EXPORT DownloadFileImpl : public DownloadFile { void RenameAndUniquify(const base::FilePath& full_path, const RenameCompletionCallback& callback) override; void RenameAndAnnotate(const base::FilePath& full_path, + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url, const RenameCompletionCallback& callback) override; void Detach() override; void Cancel() override; - base::FilePath FullPath() const override; + const base::FilePath& FullPath() const override; bool InProgress() const override; - int64_t CurrentSpeed() const override; - bool GetHash(std::string* hash) override; - std::string GetHashState() override; - void SetClientGuid(const std::string& guid) override; protected: // For test class overrides. @@ -85,20 +81,29 @@ class CONTENT_EXPORT DownloadFileImpl : public DownloadFile { ANNOTATE_WITH_SOURCE_INFORMATION = 1 << 1 }; - // Rename file_ to |new_path|. - // |option| specifies additional operations to be performed during the rename. - // See RenameOption above. - // |retries_left| indicates how many times to retry the operation if the - // rename fails with a transient error. - // |time_of_first_failure| Set to an empty base::TimeTicks during the first - // call. Once the first failure is seen, subsequent calls of - // RenameWithRetryInternal will have a non-empty value keeping track of - // the time of first observed failure. Used for UMA. - void RenameWithRetryInternal(const base::FilePath& new_path, - RenameOption option, - int retries_left, - base::TimeTicks time_of_first_failure, - const RenameCompletionCallback& callback); + struct RenameParameters { + RenameParameters(RenameOption option, + const base::FilePath& new_path, + const RenameCompletionCallback& completion_callback); + ~RenameParameters(); + + RenameOption option; + base::FilePath new_path; + std::string client_guid; // See BaseFile::AnnotateWithSourceInformation() + GURL source_url; // See BaseFile::AnnotateWithSourceInformation() + GURL referrer_url; // See BaseFile::AnnotateWithSourceInformation() + int retries_left; // RenameWithRetryInternal() will + // automatically retry until this + // count reaches 0. Each attempt + // decrements this counter. + base::TimeTicks time_of_first_failure; // Set to empty at first, but is set + // when a failure is first + // encountered. Used for UMA. + RenameCompletionCallback completion_callback; + }; + + // Rename file_ based on |parameters|. + void RenameWithRetryInternal(scoped_ptr<RenameParameters> parameters); // Send an update on our progress. void SendUpdate(); @@ -110,6 +115,11 @@ class CONTENT_EXPORT DownloadFileImpl : public DownloadFile { // The base file instance. BaseFile file_; + // DownloadSaveInfo provided during construction. Since the DownloadFileImpl + // can be created on any thread, this holds the save_info_ until it can be + // used to initialize file_ on the FILE thread. + scoped_ptr<DownloadSaveInfo> save_info_; + // The default directory for creating the download file. base::FilePath default_download_directory_; diff --git a/chromium/content/browser/download/download_file_unittest.cc b/chromium/content/browser/download/download_file_unittest.cc index 9630a49b066..d24eeb81392 100644 --- a/chromium/content/browser/download/download_file_unittest.cc +++ b/chromium/content/browser/download/download_file_unittest.cc @@ -4,7 +4,9 @@ #include <stddef.h> #include <stdint.h> + #include <utility> +#include <vector> #include "base/files/file.h" #include "base/files/file_util.h" @@ -18,9 +20,9 @@ #include "content/browser/browser_thread_impl.h" #include "content/browser/byte_stream.h" #include "content/browser/download/download_create_info.h" +#include "content/browser/download/download_destination_observer.h" #include "content/browser/download/download_file_impl.h" #include "content/browser/download/download_request_handle.h" -#include "content/public/browser/download_destination_observer.h" #include "content/public/browser/download_interrupt_reasons.h" #include "content/public/browser/download_manager.h" #include "content/public/test/mock_download_manager.h" @@ -41,6 +43,14 @@ using ::testing::StrictMock; namespace content { namespace { +std::string GetHexEncodedHashValue(crypto::SecureHash* hash_state) { + if (!hash_state) + return std::string(); + std::vector<char> hash_value(hash_state->GetHashLength()); + hash_state->Finish(&hash_value.front(), hash_value.size()); + return base::HexEncode(&hash_value.front(), hash_value.size()); +} + class MockByteStreamReader : public ByteStreamReader { public: MockByteStreamReader() {} @@ -55,21 +65,33 @@ class MockByteStreamReader : public ByteStreamReader { class MockDownloadDestinationObserver : public DownloadDestinationObserver { public: - MOCK_METHOD3(DestinationUpdate, void(int64_t, int64_t, const std::string&)); - MOCK_METHOD1(DestinationError, void(DownloadInterruptReason)); - MOCK_METHOD1(DestinationCompleted, void(const std::string&)); + MOCK_METHOD2(DestinationUpdate, void(int64_t, int64_t)); + void DestinationError(DownloadInterruptReason reason, + int64_t bytes_so_far, + scoped_ptr<crypto::SecureHash> hash_state) override { + MockDestinationError( + reason, bytes_so_far, GetHexEncodedHashValue(hash_state.get())); + } + void DestinationCompleted( + int64_t total_bytes, + scoped_ptr<crypto::SecureHash> hash_state) override { + MockDestinationCompleted(total_bytes, + GetHexEncodedHashValue(hash_state.get())); + } + + MOCK_METHOD3(MockDestinationError, + void(DownloadInterruptReason, int64_t, const std::string&)); + MOCK_METHOD2(MockDestinationCompleted, void(int64_t, const std::string&)); // Doesn't override any methods in the base class. Used to make sure // that the last DestinationUpdate before a Destination{Completed,Error} // had the right values. - MOCK_METHOD3(CurrentUpdateStatus, void(int64_t, int64_t, const std::string&)); + MOCK_METHOD2(CurrentUpdateStatus, void(int64_t, int64_t)); }; MATCHER(IsNullCallback, "") { return (arg.is_null()); } -typedef void (DownloadFile::*DownloadFileRenameMethodType)( - const base::FilePath&, - const DownloadFile::RenameCompletionCallback&); +enum DownloadFileRenameMethodType { RENAME_AND_UNIQUIFY, RENAME_AND_ANNOTATE }; // This is a test DownloadFileImpl that has no retry delay and, on Posix, // retries renames failed due to ACCESS_DENIED. @@ -77,17 +99,11 @@ class TestDownloadFileImpl : public DownloadFileImpl { public: TestDownloadFileImpl(scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_downloads_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, scoped_ptr<ByteStreamReader> stream, const net::BoundNetLog& bound_net_log, base::WeakPtr<DownloadDestinationObserver> observer) : DownloadFileImpl(std::move(save_info), default_downloads_directory, - url, - referrer_url, - calculate_hash, std::move(stream), bound_net_log, observer) {} @@ -111,11 +127,11 @@ class TestDownloadFileImpl : public DownloadFileImpl { class DownloadFileTest : public testing::Test { public: - - static const char* kTestData1; - static const char* kTestData2; - static const char* kTestData3; - static const char* kDataHash; + static const char kTestData1[]; + static const char kTestData2[]; + static const char kTestData3[]; + static const char kDataHash[]; + static const char kEmptyHash[]; static const uint32_t kDummyDownloadId; static const int kDummyChildId; static const int kDummyRequestId; @@ -126,27 +142,23 @@ class DownloadFileTest : public testing::Test { input_stream_(NULL), bytes_(-1), bytes_per_sec_(-1), - hash_state_("xyzzy"), ui_thread_(BrowserThread::UI, &loop_), file_thread_(BrowserThread::FILE, &loop_) { } ~DownloadFileTest() override {} - void SetUpdateDownloadInfo(int64_t bytes, - int64_t bytes_per_sec, - const std::string& hash_state) { + void SetUpdateDownloadInfo(int64_t bytes, int64_t bytes_per_sec) { bytes_ = bytes; bytes_per_sec_ = bytes_per_sec; - hash_state_ = hash_state; } void ConfirmUpdateDownloadInfo() { - observer_->CurrentUpdateStatus(bytes_, bytes_per_sec_, hash_state_); + observer_->CurrentUpdateStatus(bytes_, bytes_per_sec_); } void SetUp() override { - EXPECT_CALL(*(observer_.get()), DestinationUpdate(_, _, _)) + EXPECT_CALL(*(observer_.get()), DestinationUpdate(_, _)) .Times(AnyNumber()) .WillRepeatedly(Invoke(this, &DownloadFileTest::SetUpdateDownloadInfo)); } @@ -176,15 +188,12 @@ class DownloadFileTest : public testing::Test { .RetiresOnSaturation(); scoped_ptr<DownloadSaveInfo> save_info(new DownloadSaveInfo()); - scoped_ptr<TestDownloadFileImpl> download_file_impl( - new TestDownloadFileImpl( - std::move(save_info), base::FilePath(), - GURL(), // Source - GURL(), // Referrer - calculate_hash, scoped_ptr<ByteStreamReader>(input_stream_), - net::BoundNetLog(), observer_factory_.GetWeakPtr())); - download_file_impl->SetClientGuid("12345678-ABCD-1234-DCBA-123456789ABC"); - download_file_ = std::move(download_file_impl); + download_file_.reset( + new TestDownloadFileImpl(std::move(save_info), + base::FilePath(), + scoped_ptr<ByteStreamReader>(input_stream_), + net::BoundNetLog(), + observer_factory_.GetWeakPtr())); EXPECT_CALL(*input_stream_, Read(_, _)) .WillOnce(Return(ByteStreamReader::STREAM_EMPTY)) @@ -269,19 +278,21 @@ class DownloadFileTest : public testing::Test { } void FinishStream(DownloadInterruptReason interrupt_reason, - bool check_observer) { + bool check_observer, + const std::string& expected_hash) { ::testing::Sequence s1; SetupFinishStream(interrupt_reason, s1); sink_callback_.Run(); VerifyStreamAndSize(); if (check_observer) { - EXPECT_CALL(*(observer_.get()), DestinationCompleted(_)); + EXPECT_CALL(*(observer_.get()), + MockDestinationCompleted(_, expected_hash)); loop_.RunUntilIdle(); ::testing::Mock::VerifyAndClearExpectations(observer_.get()); - EXPECT_CALL(*(observer_.get()), DestinationUpdate(_, _, _)) + EXPECT_CALL(*(observer_.get()), DestinationUpdate(_, _)) .Times(AnyNumber()) - .WillRepeatedly(Invoke(this, - &DownloadFileTest::SetUpdateDownloadInfo)); + .WillRepeatedly( + Invoke(this, &DownloadFileTest::SetUpdateDownloadInfo)); } } @@ -289,14 +300,14 @@ class DownloadFileTest : public testing::Test { const base::FilePath& full_path, base::FilePath* result_path_p) { return InvokeRenameMethodAndWaitForCallback( - &DownloadFile::RenameAndUniquify, full_path, result_path_p); + RENAME_AND_UNIQUIFY, full_path, result_path_p); } DownloadInterruptReason RenameAndAnnotate( const base::FilePath& full_path, base::FilePath* result_path_p) { return InvokeRenameMethodAndWaitForCallback( - &DownloadFile::RenameAndAnnotate, full_path, result_path_p); + RENAME_AND_ANNOTATE, full_path, result_path_p); } void ExpectPermissionError(DownloadInterruptReason err) { @@ -306,20 +317,40 @@ class DownloadFileTest : public testing::Test { } protected: + void InvokeRenameMethod( + DownloadFileRenameMethodType method, + const base::FilePath& full_path, + const DownloadFile::RenameCompletionCallback& completion_callback) { + switch (method) { + case RENAME_AND_UNIQUIFY: + download_file_->RenameAndUniquify(full_path, completion_callback); + break; + + case RENAME_AND_ANNOTATE: + download_file_->RenameAndAnnotate( + full_path, + "12345678-ABCD-1234-DCBA-123456789ABC", + GURL(), + GURL(), + completion_callback); + break; + } + } + DownloadInterruptReason InvokeRenameMethodAndWaitForCallback( DownloadFileRenameMethodType method, const base::FilePath& full_path, base::FilePath* result_path_p) { DownloadInterruptReason result_reason(DOWNLOAD_INTERRUPT_REASON_NONE); base::FilePath result_path; - base::RunLoop loop_runner; - ((*download_file_).*method)(full_path, - base::Bind(&DownloadFileTest::SetRenameResult, - base::Unretained(this), - loop_runner.QuitClosure(), - &result_reason, - result_path_p)); + DownloadFile::RenameCompletionCallback completion_callback = + base::Bind(&DownloadFileTest::SetRenameResult, + base::Unretained(this), + loop_runner.QuitClosure(), + &result_reason, + result_path_p); + InvokeRenameMethod(method, full_path, completion_callback); loop_runner.Run(); return result_reason; } @@ -340,7 +371,6 @@ class DownloadFileTest : public testing::Test { // Latest update sent to the observer. int64_t bytes_; int64_t bytes_per_sec_; - std::string hash_state_; base::MessageLoop loop_; @@ -390,15 +420,17 @@ class DownloadFileTestWithRename // the value parameter. INSTANTIATE_TEST_CASE_P(DownloadFile, DownloadFileTestWithRename, - ::testing::Values(&DownloadFile::RenameAndAnnotate, - &DownloadFile::RenameAndUniquify)); + ::testing::Values(RENAME_AND_ANNOTATE, + RENAME_AND_UNIQUIFY)); -const char* DownloadFileTest::kTestData1 = +const char DownloadFileTest::kTestData1[] = "Let's write some data to the file!\n"; -const char* DownloadFileTest::kTestData2 = "Writing more data.\n"; -const char* DownloadFileTest::kTestData3 = "Final line."; -const char* DownloadFileTest::kDataHash = +const char DownloadFileTest::kTestData2[] = "Writing more data.\n"; +const char DownloadFileTest::kTestData3[] = "Final line."; +const char DownloadFileTest::kDataHash[] = "CBF68BF10F8003DB86B31343AFAC8C7175BD03FB5FC905650F8C80AF087443A8"; +const char DownloadFileTest::kEmptyHash[] = + "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"; const uint32_t DownloadFileTest::kDummyDownloadId = 23; const int DownloadFileTest::kDummyChildId = 3; @@ -456,10 +488,7 @@ TEST_P(DownloadFileTestWithRename, RenameFileFinal) { EXPECT_FALSE(base::PathExists(path_2)); EXPECT_TRUE(base::PathExists(path_3)); - // Should not be able to get the hash until the file is closed. - std::string hash; - EXPECT_FALSE(download_file_->GetHash(&hash)); - FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kDataHash); loop_.RunUntilIdle(); // Rename the file after downloading all the data and closing the file. @@ -473,10 +502,6 @@ TEST_P(DownloadFileTestWithRename, RenameFileFinal) { EXPECT_FALSE(base::PathExists(path_3)); EXPECT_TRUE(base::PathExists(path_4)); - // Check the hash. - EXPECT_TRUE(download_file_->GetHash(&hash)); - EXPECT_EQ(kDataHash, base::HexEncode(hash.data(), hash.size())); - DestroyDownloadFile(0); } @@ -504,7 +529,7 @@ TEST_F(DownloadFileTest, RenameOverwrites) { ASSERT_TRUE(base::ReadFileToString(new_path, &file_contents)); EXPECT_NE(std::string(file_data), file_contents); - FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash); loop_.RunUntilIdle(); DestroyDownloadFile(0); } @@ -529,7 +554,7 @@ TEST_F(DownloadFileTest, RenameUniquifies) { EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, RenameAndUniquify(path_1, NULL)); EXPECT_TRUE(base::PathExists(path_1_suffixed)); - FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash); loop_.RunUntilIdle(); DestroyDownloadFile(0); } @@ -546,7 +571,7 @@ TEST_F(DownloadFileTest, RenameRecognizesSelfConflict) { RenameAndUniquify(initial_path, &new_path)); EXPECT_TRUE(base::PathExists(initial_path)); - FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash); loop_.RunUntilIdle(); DestroyDownloadFile(0); EXPECT_EQ(initial_path.value(), new_path.value()); @@ -581,7 +606,7 @@ TEST_P(DownloadFileTestWithRename, RenameError) { EXPECT_FALSE(base::PathExists(target_path_suffixed)); } - FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash); loop_.RunUntilIdle(); DestroyDownloadFile(0); } @@ -644,10 +669,11 @@ TEST_P(DownloadFileTestWithRename, RenameWithErrorRetry) { // The Rename() should fail here and enqueue a retry task without invoking // the completion callback. - ((*download_file_).*GetParam())(target_path, - base::Bind(&TestRenameCompletionCallback, - succeeding_run.QuitClosure(), - &did_run_callback)); + InvokeRenameMethod(GetParam(), + target_path, + base::Bind(&TestRenameCompletionCallback, + succeeding_run.QuitClosure(), + &did_run_callback)); EXPECT_FALSE(did_run_callback); base::RunLoop first_failing_run; @@ -673,7 +699,7 @@ TEST_P(DownloadFileTestWithRename, RenameWithErrorRetry) { succeeding_run.Run(); EXPECT_TRUE(did_run_callback); - FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash); loop_.RunUntilIdle(); DestroyDownloadFile(0); } @@ -688,10 +714,8 @@ TEST_F(DownloadFileTest, StreamEmptySuccess) { // do anything. AppendDataToFile(NULL, 0); - // Finish the download this way and make sure we see it on the - // observer. - EXPECT_CALL(*(observer_.get()), DestinationCompleted(_)); - FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, false); + // Finish the download this way and make sure we see it on the observer. + FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true, kEmptyHash); loop_.RunUntilIdle(); DestroyDownloadFile(0); @@ -704,9 +728,10 @@ TEST_F(DownloadFileTest, StreamEmptyError) { // Finish the download in error and make sure we see it on the // observer. - EXPECT_CALL(*(observer_.get()), - DestinationError( - DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED)) + EXPECT_CALL( + *(observer_.get()), + MockDestinationError( + DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, 0, kEmptyHash)) .WillOnce(InvokeWithoutArgs( this, &DownloadFileTest::ConfirmUpdateDownloadInfo)); @@ -715,9 +740,10 @@ TEST_F(DownloadFileTest, StreamEmptyError) { // the last one may have the correct information even if the failure // doesn't produce an update, as the timer update may have triggered at the // same time. - EXPECT_CALL(*(observer_.get()), CurrentUpdateStatus(0, _, _)); + EXPECT_CALL(*(observer_.get()), CurrentUpdateStatus(0, _)); - FinishStream(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, false); + FinishStream( + DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, false, kEmptyHash); loop_.RunUntilIdle(); @@ -733,7 +759,7 @@ TEST_F(DownloadFileTest, StreamNonEmptySuccess) { ::testing::Sequence s1; SetupDataAppend(chunks1, 2, s1); SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, s1); - EXPECT_CALL(*(observer_.get()), DestinationCompleted(_)); + EXPECT_CALL(*(observer_.get()), MockDestinationCompleted(_, _)); sink_callback_.Run(); VerifyStreamAndSize(); loop_.RunUntilIdle(); @@ -751,8 +777,8 @@ TEST_F(DownloadFileTest, StreamNonEmptyError) { SetupFinishStream(DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, s1); EXPECT_CALL(*(observer_.get()), - DestinationError( - DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED)) + MockDestinationError( + DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED, _, _)) .WillOnce(InvokeWithoutArgs( this, &DownloadFileTest::ConfirmUpdateDownloadInfo)); @@ -762,8 +788,7 @@ TEST_F(DownloadFileTest, StreamNonEmptyError) { // doesn't produce an update, as the timer update may have triggered at the // same time. EXPECT_CALL(*(observer_.get()), - CurrentUpdateStatus(strlen(kTestData1) + strlen(kTestData2), - _, _)); + CurrentUpdateStatus(strlen(kTestData1) + strlen(kTestData2), _)); sink_callback_.Run(); loop_.RunUntilIdle(); @@ -771,26 +796,4 @@ TEST_F(DownloadFileTest, StreamNonEmptyError) { DestroyDownloadFile(0); } -// Send some data, wait 3/4s of a second, run the message loop, and -// confirm the values the observer received are correct. -TEST_F(DownloadFileTest, ConfirmUpdate) { - CreateDownloadFile(0, true); - - const char* chunks1[] = { kTestData1, kTestData2 }; - AppendDataToFile(chunks1, 2); - - // Run the message loops for 750ms and check for results. - loop_.task_runner()->PostDelayedTask(FROM_HERE, - base::MessageLoop::QuitWhenIdleClosure(), - base::TimeDelta::FromMilliseconds(750)); - loop_.Run(); - - EXPECT_EQ(static_cast<int64_t>(strlen(kTestData1) + strlen(kTestData2)), - bytes_); - EXPECT_EQ(download_file_->GetHashState(), hash_state_); - - FinishStream(DOWNLOAD_INTERRUPT_REASON_NONE, true); - DestroyDownloadFile(0); -} - } // namespace content diff --git a/chromium/content/browser/download/download_interrupt_reasons_impl.cc b/chromium/content/browser/download/download_interrupt_reasons_impl.cc index 2ed8f147ee0..406c4e9d33a 100644 --- a/chromium/content/browser/download/download_interrupt_reasons_impl.cc +++ b/chromium/content/browser/download/download_interrupt_reasons_impl.cc @@ -83,7 +83,8 @@ DownloadInterruptReason ConvertNetErrorToInterruptReason( case net::ERR_TIMED_OUT: return DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT; - // The network connection has been lost. + // The network connection was lost or changed. + case net::ERR_NETWORK_CHANGED: case net::ERR_INTERNET_DISCONNECTED: return DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED; diff --git a/chromium/content/browser/download/download_item_factory.h b/chromium/content/browser/download/download_item_factory.h index 0069e43422e..caf8ef8ce4b 100644 --- a/chromium/content/browser/download/download_item_factory.h +++ b/chromium/content/browser/download/download_item_factory.h @@ -41,6 +41,7 @@ public: virtual DownloadItemImpl* CreatePersistedItem( DownloadItemImplDelegate* delegate, + const std::string& guid, uint32_t download_id, const base::FilePath& current_path, const base::FilePath& target_path, @@ -54,6 +55,7 @@ public: const std::string& last_modified, int64_t received_bytes, int64_t total_bytes, + const std::string& hash, DownloadItem::DownloadState state, DownloadDangerType danger_type, DownloadInterruptReason interrupt_reason, diff --git a/chromium/content/browser/download/download_item_impl.cc b/chromium/content/browser/download/download_item_impl.cc index cef57731792..08d90390a60 100644 --- a/chromium/content/browser/download/download_item_impl.cc +++ b/chromium/content/browser/download/download_item_impl.cc @@ -29,15 +29,18 @@ #include "base/bind.h" #include "base/files/file_util.h" #include "base/format_macros.h" +#include "base/guid.h" #include "base/logging.h" #include "base/metrics/histogram.h" #include "base/stl_util.h" +#include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "content/browser/download/download_create_info.h" #include "content/browser/download/download_file.h" #include "content/browser/download/download_interrupt_reasons_impl.h" #include "content/browser/download/download_item_impl_delegate.h" +#include "content/browser/download/download_net_log_parameters.h" #include "content/browser/download/download_request_handle.h" #include "content/browser/download/download_stats.h" #include "content/browser/renderer_host/render_view_host_impl.h" @@ -50,7 +53,6 @@ #include "content/public/browser/download_url_parameters.h" #include "content/public/common/content_features.h" #include "content/public/common/referrer.h" -#include "net/base/net_util.h" namespace content { @@ -99,13 +101,12 @@ bool IsDownloadResumptionEnabled() { const uint32_t DownloadItem::kInvalidId = 0; -const char DownloadItem::kEmptyFileHash[] = ""; - // The maximum number of attempts we will make to resume automatically. const int DownloadItemImpl::kMaxAutoResumeAttempts = 5; // Constructor for reading from the history service. DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate, + const std::string& guid, uint32_t download_id, const base::FilePath& current_path, const base::FilePath& target_path, @@ -119,27 +120,20 @@ DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate, const std::string& last_modified, int64_t received_bytes, int64_t total_bytes, + const std::string& hash, DownloadItem::DownloadState state, DownloadDangerType danger_type, DownloadInterruptReason interrupt_reason, bool opened, const net::BoundNetLog& bound_net_log) - : is_save_package_download_(false), + : guid_(base::ToUpperASCII(guid)), download_id_(download_id), - current_path_(current_path), target_path_(target_path), - target_disposition_(TARGET_DISPOSITION_OVERWRITE), url_chain_(url_chain), referrer_url_(referrer_url), - transition_type_(ui::PAGE_TRANSITION_LINK), - has_user_gesture_(false), mime_type_(mime_type), original_mime_type_(original_mime_type), total_bytes_(total_bytes), - received_bytes_(received_bytes), - bytes_per_sec_(0), - last_modified_time_(last_modified), - etag_(etag), last_reason_(interrupt_reason), start_tick_(base::TimeTicks()), state_(ExternalToInternalState(state)), @@ -147,20 +141,19 @@ DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate, start_time_(start_time), end_time_(end_time), delegate_(delegate), - is_paused_(false), - auto_resume_count_(0), - open_when_complete_(false), - file_externally_removed_(false), - auto_opened_(false), - is_temporary_(false), - all_data_saved_(state == COMPLETE), - destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE), opened_(opened), - delegate_delayed_complete_(false), + current_path_(current_path), + received_bytes_(received_bytes), + all_data_saved_(state == COMPLETE), + hash_(hash), + last_modified_time_(last_modified), + etag_(etag), bound_net_log_(bound_net_log), weak_ptr_factory_(this) { delegate_->Attach(); - DCHECK_NE(IN_PROGRESS_INTERNAL, state_); + DCHECK(state_ == COMPLETE_INTERNAL || state_ == INTERRUPTED_INTERNAL || + state_ == CANCELLED_INTERNAL); + DCHECK(base::IsValidGUID(guid_)); Init(false /* not actively downloading */, SRC_HISTORY_IMPORT); } @@ -169,7 +162,7 @@ DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate, uint32_t download_id, const DownloadCreateInfo& info, const net::BoundNetLog& bound_net_log) - : is_save_package_download_(false), + : guid_(base::ToUpperASCII(base::GenerateGUID())), download_id_(download_id), target_disposition_((info.save_info->prompt_for_save_location) ? TARGET_DISPOSITION_PROMPT @@ -187,26 +180,14 @@ DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate, original_mime_type_(info.original_mime_type), remote_address_(info.remote_address), total_bytes_(info.total_bytes), - received_bytes_(0), - bytes_per_sec_(0), - last_modified_time_(info.last_modified), - etag_(info.etag), - last_reason_(DOWNLOAD_INTERRUPT_REASON_NONE), + last_reason_(info.result), start_tick_(base::TimeTicks::Now()), - state_(IN_PROGRESS_INTERNAL), - danger_type_(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS), + state_(INITIAL_INTERNAL), start_time_(info.start_time), delegate_(delegate), - is_paused_(false), - auto_resume_count_(0), - open_when_complete_(false), - file_externally_removed_(false), - auto_opened_(false), is_temporary_(!info.save_info->file_path.empty()), - all_data_saved_(false), - destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE), - opened_(false), - delegate_delayed_complete_(false), + last_modified_time_(info.last_modified), + etag_(info.etag), bound_net_log_(bound_net_log), weak_ptr_factory_(this) { delegate_->Attach(); @@ -233,35 +214,17 @@ DownloadItemImpl::DownloadItemImpl( const net::BoundNetLog& bound_net_log) : is_save_package_download_(true), request_handle_(std::move(request_handle)), + guid_(base::ToUpperASCII(base::GenerateGUID())), download_id_(download_id), - current_path_(path), target_path_(path), - target_disposition_(TARGET_DISPOSITION_OVERWRITE), url_chain_(1, url), - referrer_url_(GURL()), - transition_type_(ui::PAGE_TRANSITION_LINK), - has_user_gesture_(false), mime_type_(mime_type), original_mime_type_(mime_type), - total_bytes_(0), - received_bytes_(0), - bytes_per_sec_(0), - last_reason_(DOWNLOAD_INTERRUPT_REASON_NONE), start_tick_(base::TimeTicks::Now()), state_(IN_PROGRESS_INTERNAL), - danger_type_(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS), start_time_(base::Time::Now()), delegate_(delegate), - is_paused_(false), - auto_resume_count_(0), - open_when_complete_(false), - file_externally_removed_(false), - auto_opened_(false), - is_temporary_(false), - all_data_saved_(false), - destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE), - opened_(false), - delegate_delayed_complete_(false), + current_path_(path), bound_net_log_(bound_net_log), weak_ptr_factory_(this) { delegate_->Attach(); @@ -294,6 +257,7 @@ void DownloadItemImpl::RemoveObserver(Observer* observer) { void DownloadItemImpl::UpdateObservers() { DCHECK_CURRENTLY_ON(BrowserThread::UI); + DVLOG(20) << __FUNCTION__ << "()"; FOR_EACH_OBSERVER(Observer, observers_, OnDownloadUpdated(this)); } @@ -317,7 +281,9 @@ void DownloadItemImpl::ValidateDangerousDownload() { net::NetLog::TYPE_DOWNLOAD_ITEM_SAFETY_STATE_UPDATED, base::Bind(&ItemCheckedNetLogCallback, GetDangerType())); - UpdateObservers(); + UpdateObservers(); // TODO(asanka): This is potentially unsafe. The download + // may not be in a consistent state or around at all after + // invoking observers. http://crbug.com/586610 MaybeCompleteDownload(); } @@ -327,6 +293,7 @@ void DownloadItemImpl::StealDangerousDownload( DVLOG(20) << __FUNCTION__ << "() download = " << DebugString(true); DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(IsDangerous()); + if (download_file_) { BrowserThread::PostTaskAndReplyWithResult( BrowserThread::FILE, @@ -345,17 +312,50 @@ void DownloadItemImpl::Pause() { DCHECK_CURRENTLY_ON(BrowserThread::UI); // Ignore irrelevant states. - if (state_ != IN_PROGRESS_INTERNAL || is_paused_) + if (is_paused_) return; - request_handle_->PauseRequest(); - is_paused_ = true; - UpdateObservers(); + switch (state_) { + case CANCELLED_INTERNAL: + case COMPLETE_INTERNAL: + case COMPLETING_INTERNAL: + case INITIAL_INTERNAL: + case INTERRUPTED_INTERNAL: + case INTERRUPTED_TARGET_PENDING_INTERNAL: + case RESUMING_INTERNAL: + // No active request. + // TODO(asanka): In the case of RESUMING_INTERNAL, consider setting + // is_paused_ even if there's no request currently associated with this + // DII. When a request is assigned (due to a resumption, for example) we + // can honor the is_paused_ setting. + return; + + case IN_PROGRESS_INTERNAL: + case TARGET_PENDING_INTERNAL: + request_handle_->PauseRequest(); + is_paused_ = true; + UpdateObservers(); + return; + + case MAX_DOWNLOAD_INTERNAL_STATE: + case TARGET_RESOLVED_INTERNAL: + NOTREACHED(); + } } void DownloadItemImpl::Resume() { DCHECK_CURRENTLY_ON(BrowserThread::UI); + DVLOG(20) << __FUNCTION__ << "() download = " << DebugString(true); switch (state_) { + case CANCELLED_INTERNAL: // Nothing to resume. + case COMPLETE_INTERNAL: + case COMPLETING_INTERNAL: + case INITIAL_INTERNAL: + case INTERRUPTED_TARGET_PENDING_INTERNAL: + case RESUMING_INTERNAL: // Resumption in progress. + return; + + case TARGET_PENDING_INTERNAL: case IN_PROGRESS_INTERNAL: if (!is_paused_) return; @@ -364,70 +364,25 @@ void DownloadItemImpl::Resume() { UpdateObservers(); return; - case COMPLETING_INTERNAL: - case COMPLETE_INTERNAL: - case CANCELLED_INTERNAL: - case RESUMING_INTERNAL: - return; - case INTERRUPTED_INTERNAL: auto_resume_count_ = 0; // User input resets the counter. ResumeInterruptedDownload(); + UpdateObservers(); return; case MAX_DOWNLOAD_INTERNAL_STATE: + case TARGET_RESOLVED_INTERNAL: NOTREACHED(); } } void DownloadItemImpl::Cancel(bool user_cancel) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - DVLOG(20) << __FUNCTION__ << "() download = " << DebugString(true); - if (state_ != IN_PROGRESS_INTERNAL && - state_ != INTERRUPTED_INTERNAL && - state_ != RESUMING_INTERNAL) { - // Small downloads might be complete before this method has a chance to run. - return; - } - - if (IsDangerous()) { - RecordDangerousDownloadDiscard( - user_cancel ? DOWNLOAD_DISCARD_DUE_TO_USER_ACTION - : DOWNLOAD_DISCARD_DUE_TO_SHUTDOWN, - GetDangerType(), - GetTargetFilePath()); - } - - last_reason_ = user_cancel ? DOWNLOAD_INTERRUPT_REASON_USER_CANCELED - : DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN; - - RecordDownloadCount(CANCELLED_COUNT); - - // TODO(rdsmith/benjhayden): Remove condition as part of - // |SavePackage| integration. - // |download_file_| can be NULL if Interrupt() is called after the - // download file has been released. - if (!is_save_package_download_ && download_file_) - ReleaseDownloadFile(true); - - if (state_ == IN_PROGRESS_INTERNAL) { - // Cancel the originating URL request unless it's already been cancelled - // by interrupt. - request_handle_->CancelRequest(); - } - - // Remove the intermediate file if we are cancelling an interrupted download. - // Continuable interruptions leave the intermediate file around. - if ((state_ == INTERRUPTED_INTERNAL || state_ == RESUMING_INTERNAL) && - !current_path_.empty()) { - BrowserThread::PostTask( - BrowserThread::FILE, FROM_HERE, - base::Bind(base::IgnoreResult(&DeleteDownloadedFile), current_path_)); - current_path_.clear(); - } - - TransitionTo(CANCELLED_INTERNAL, UPDATE_OBSERVERS); + InterruptAndDiscardPartialState( + user_cancel ? DOWNLOAD_INTERRUPT_REASON_USER_CANCELED + : DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN); + UpdateObservers(); } void DownloadItemImpl::Remove() { @@ -435,7 +390,8 @@ void DownloadItemImpl::Remove() { DCHECK_CURRENTLY_ON(BrowserThread::UI); delegate_->AssertStateConsistent(this); - Cancel(true); + InterruptAndDiscardPartialState(DOWNLOAD_INTERRUPT_REASON_USER_CANCELED); + UpdateObservers(); delegate_->AssertStateConsistent(this); NotifyRemoved(); @@ -478,6 +434,10 @@ uint32_t DownloadItemImpl::GetId() const { return download_id_; } +const std::string& DownloadItemImpl::GetGuid() const { + return guid_; +} + DownloadItem::DownloadState DownloadItemImpl::GetState() const { return InternalToExternalState(state_); } @@ -495,26 +455,45 @@ bool DownloadItemImpl::IsTemporary() const { } bool DownloadItemImpl::CanResume() const { - if ((GetState() == IN_PROGRESS) && IsPaused()) - return true; - - if (state_ != INTERRUPTED_INTERNAL) - return false; + DCHECK_CURRENTLY_ON(BrowserThread::UI); + switch (state_) { + case INITIAL_INTERNAL: + case COMPLETING_INTERNAL: + case COMPLETE_INTERNAL: + case CANCELLED_INTERNAL: + case RESUMING_INTERNAL: + case INTERRUPTED_TARGET_PENDING_INTERNAL: + return false; - // We currently only support HTTP(S) requests for download resumption. - if (!GetURL().SchemeIsHTTPOrHTTPS()) - return false; + case TARGET_PENDING_INTERNAL: + case TARGET_RESOLVED_INTERNAL: + case IN_PROGRESS_INTERNAL: + return is_paused_; + + case INTERRUPTED_INTERNAL: { + ResumeMode resume_mode = GetResumeMode(); + // Only allow Resume() calls if the resumption mode requires a user + // action. + return IsDownloadResumptionEnabled() && + (resume_mode == RESUME_MODE_USER_RESTART || + resume_mode == RESUME_MODE_USER_CONTINUE); + } - ResumeMode resume_mode = GetResumeMode(); - return IsDownloadResumptionEnabled() && - (resume_mode == RESUME_MODE_USER_RESTART || - resume_mode == RESUME_MODE_USER_CONTINUE); + case MAX_DOWNLOAD_INTERNAL_STATE: + NOTREACHED(); + } + return false; } bool DownloadItemImpl::IsDone() const { switch (state_) { - case IN_PROGRESS_INTERNAL: + case INITIAL_INTERNAL: case COMPLETING_INTERNAL: + case RESUMING_INTERNAL: + case TARGET_PENDING_INTERNAL: + case INTERRUPTED_TARGET_PENDING_INTERNAL: + case TARGET_RESOLVED_INTERNAL: + case IN_PROGRESS_INTERNAL: return false; case COMPLETE_INTERNAL: @@ -524,14 +503,10 @@ bool DownloadItemImpl::IsDone() const { case INTERRUPTED_INTERNAL: return !CanResume(); - case RESUMING_INTERNAL: - return false; - case MAX_DOWNLOAD_INTERNAL_STATE: - break; + NOTREACHED(); } - NOTREACHED(); - return true; + return false; } const GURL& DownloadItemImpl::GetURL() const { @@ -628,10 +603,6 @@ const std::string& DownloadItemImpl::GetHash() const { return hash_; } -const std::string& DownloadItemImpl::GetHashState() const { - return hash_state_; -} - bool DownloadItemImpl::GetFileExternallyRemoved() const { return file_externally_removed_; } @@ -770,6 +741,14 @@ WebContents* DownloadItemImpl::GetWebContents() const { void DownloadItemImpl::OnContentCheckCompleted(DownloadDangerType danger_type) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(AllDataSaved()); + + // Danger type is only allowed to be set on an active download after all data + // has been saved. This excludes all other states. In particular, + // OnContentCheckCompleted() isn't allowed on an INTERRUPTED download since + // such an interruption would need to happen between OnAllDataSaved() and + // OnContentCheckCompleted() during which no disk or network activity + // should've taken place. + DCHECK_EQ(state_, IN_PROGRESS_INTERNAL); DVLOG(20) << __FUNCTION__ << " danger_type=" << danger_type << " download=" << DebugString(true); SetDangerType(danger_type); @@ -815,8 +794,7 @@ std::string DownloadItemImpl::DebugString(bool verbose) const { if (verbose) { description += base::StringPrintf( - " total = %" PRId64 - " received = %" PRId64 + " total = %" PRId64 " received = %" PRId64 " reason = %s" " paused = %c" " resume_mode = %s" @@ -827,7 +805,8 @@ std::string DownloadItemImpl::DebugString(bool verbose) const { " etag = '%s'" " has_download_file = %s" " url_chain = \n\t\"%s\"\n\t" - " full_path = \"%" PRFilePath "\"\n\t" + " current_path = \"%" PRFilePath + "\"\n\t" " target_path = \"%" PRFilePath "\"", GetTotalBytes(), GetReceivedBytes(), @@ -854,62 +833,78 @@ std::string DownloadItemImpl::DebugString(bool verbose) const { DownloadItemImpl::ResumeMode DownloadItemImpl::GetResumeMode() const { DCHECK_CURRENTLY_ON(BrowserThread::UI); + + if (!IsDownloadResumptionEnabled()) + return RESUME_MODE_INVALID; + + // Only support resumption for HTTP(S). + if (!GetURL().SchemeIsHTTPOrHTTPS()) + return RESUME_MODE_INVALID; + // We can't continue without a handle on the intermediate file. // We also can't continue if we don't have some verifier to make sure // we're getting the same file. - const bool force_restart = + bool restart_required = (current_path_.empty() || (etag_.empty() && last_modified_time_.empty())); // We won't auto-restart if we've used up our attempts or the // download has been paused by user action. - const bool force_user = + bool user_action_required = (auto_resume_count_ >= kMaxAutoResumeAttempts || is_paused_); - ResumeMode mode = RESUME_MODE_INVALID; - switch(last_reason_) { case DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR: case DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT: - if (force_restart && force_user) - mode = RESUME_MODE_USER_RESTART; - else if (force_restart) - mode = RESUME_MODE_IMMEDIATE_RESTART; - else if (force_user) - mode = RESUME_MODE_USER_CONTINUE; - else - mode = RESUME_MODE_IMMEDIATE_CONTINUE; break; case DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE: + // The server disagreed with the file offset that we sent. + + case DOWNLOAD_INTERRUPT_REASON_FILE_HASH_MISMATCH: + // The file on disk was found to not match the expected hash. Discard and + // start from beginning. + case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT: - if (force_user) - mode = RESUME_MODE_USER_RESTART; - else - mode = RESUME_MODE_IMMEDIATE_RESTART; + // The [possibly persisted] file offset disagreed with the file on disk. + + // The intermediate stub is not usable and the server is responding. Hence + // retrying the request from the beginning is likely to work. + restart_required = true; break; case DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED: case DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED: case DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN: - case DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST: case DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED: + case DOWNLOAD_INTERRUPT_REASON_SERVER_UNREACHABLE: case DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN: case DOWNLOAD_INTERRUPT_REASON_CRASH: - if (force_restart) - mode = RESUME_MODE_USER_RESTART; - else - mode = RESUME_MODE_USER_CONTINUE; + // It is not clear whether attempting a resumption is acceptable at this + // time or whether it would work at all. Hence allow the user to retry the + // download manually. + user_action_required = true; + break; + + case DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE: + // There was no space. Require user interaction so that the user may, for + // example, choose a different location to store the file. Or they may + // free up some space on the targret device and retry. But try to reuse + // the partial stub. + user_action_required = true; break; case DOWNLOAD_INTERRUPT_REASON_FILE_FAILED: case DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED: - case DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE: case DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG: case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE: - mode = RESUME_MODE_USER_RESTART; + // Assume the partial stub is unusable. Also it may not be possible to + // restart immediately. + user_action_required = true; + restart_required = true; break; case DOWNLOAD_INTERRUPT_REASON_NONE: + case DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST: case DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED: case DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT: case DOWNLOAD_INTERRUPT_REASON_USER_CANCELED: @@ -918,14 +913,23 @@ DownloadItemImpl::ResumeMode DownloadItemImpl::GetResumeMode() const { case DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED: case DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM: case DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN: - mode = RESUME_MODE_INVALID; - break; + // Unhandled. + return RESUME_MODE_INVALID; } - return mode; + if (user_action_required && restart_required) + return RESUME_MODE_USER_RESTART; + + if (restart_required) + return RESUME_MODE_IMMEDIATE_RESTART; + + if (user_action_required) + return RESUME_MODE_USER_CONTINUE; + + return RESUME_MODE_IMMEDIATE_CONTINUE; } -void DownloadItemImpl::MergeOriginInfoOnResume( +void DownloadItemImpl::UpdateValidatorsOnResumption( const DownloadCreateInfo& new_create_info) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_EQ(RESUMING_INTERNAL, state_); @@ -955,8 +959,7 @@ void DownloadItemImpl::MergeOriginInfoOnResume( origin_state |= ORIGIN_STATE_ON_RESUMPTION_VALIDATORS_CHANGED; if (content_disposition_ != new_create_info.content_disposition) origin_state |= ORIGIN_STATE_ON_RESUMPTION_CONTENT_DISPOSITION_CHANGED; - RecordOriginStateOnResumption(new_create_info.save_info->offset != 0, - origin_state); + RecordOriginStateOnResumption(received_bytes_ != 0, origin_state); url_chain_.insert( url_chain_.end(), chain_iter, new_create_info.url_chain.end()); @@ -992,18 +995,20 @@ void DownloadItemImpl::SetTotalBytes(int64_t total_bytes) { total_bytes_ = total_bytes; } -void DownloadItemImpl::OnAllDataSaved(const std::string& final_hash) { +void DownloadItemImpl::OnAllDataSaved( + int64_t total_bytes, + scoped_ptr<crypto::SecureHash> hash_state) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - - DCHECK_EQ(IN_PROGRESS_INTERNAL, state_); DCHECK(!all_data_saved_); all_data_saved_ = true; - DVLOG(20) << __FUNCTION__ << " download=" << DebugString(true); - - // Store final hash and null out intermediate serialized hash state. - hash_ = final_hash; - hash_state_ = ""; + SetTotalBytes(total_bytes); + UpdateProgress(total_bytes, 0); + SetHashState(std::move(hash_state)); + hash_state_.reset(); // No need to retain hash_state_ since we are done with + // the download and don't expect to receive any more + // data. + DVLOG(20) << __FUNCTION__ << " download=" << DebugString(true); UpdateObservers(); } @@ -1012,38 +1017,28 @@ void DownloadItemImpl::MarkAsComplete() { DCHECK(all_data_saved_); end_time_ = base::Time::Now(); - TransitionTo(COMPLETE_INTERNAL, UPDATE_OBSERVERS); + TransitionTo(COMPLETE_INTERNAL); + UpdateObservers(); } void DownloadItemImpl::DestinationUpdate(int64_t bytes_so_far, - int64_t bytes_per_sec, - const std::string& hash_state) { + int64_t bytes_per_sec) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - DVLOG(20) << __FUNCTION__ << " so_far=" << bytes_so_far - << " per_sec=" << bytes_per_sec << " download=" - << DebugString(true); - - if (GetState() != IN_PROGRESS) { - // Ignore if we're no longer in-progress. This can happen if we race a - // Cancel on the UI thread with an update on the FILE thread. - // - // TODO(rdsmith): Arguably we should let this go through, as this means - // the download really did get further than we know before it was - // cancelled. But the gain isn't very large, and the code is more - // fragile if it has to support in progress updates in a non-in-progress - // state. This issue should be readdressed when we revamp performance - // reporting. - return; - } - bytes_per_sec_ = bytes_per_sec; - hash_state_ = hash_state; - received_bytes_ = bytes_so_far; + // If the download is in any other state we don't expect any + // DownloadDestinationObserver callbacks. An interruption or a cancellation + // results in a call to ReleaseDownloadFile which invalidates the weak + // reference held by the DownloadFile and hence cuts off any pending + // callbacks. + DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL); - // If we've received more data than we were expecting (bad server info?), - // revert to 'unknown size mode'. - if (received_bytes_ > total_bytes_) - total_bytes_ = 0; + // There must be no pending destination_error_. + DCHECK_EQ(destination_error_, DOWNLOAD_INTERRUPT_REASON_NONE); + DVLOG(20) << __FUNCTION__ << " so_far=" << bytes_so_far + << " per_sec=" << bytes_per_sec + << " download=" << DebugString(true); + + UpdateProgress(bytes_so_far, bytes_per_sec); if (bound_net_log_.IsCapturing()) { bound_net_log_.AddEvent( net::NetLog::TYPE_DOWNLOAD_ITEM_UPDATED, @@ -1053,21 +1048,47 @@ void DownloadItemImpl::DestinationUpdate(int64_t bytes_so_far, UpdateObservers(); } -void DownloadItemImpl::DestinationError(DownloadInterruptReason reason) { +void DownloadItemImpl::DestinationError( + DownloadInterruptReason reason, + int64_t bytes_so_far, + scoped_ptr<crypto::SecureHash> secure_hash) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + // If the download is in any other state we don't expect any + // DownloadDestinationObserver callbacks. An interruption or a cancellation + // results in a call to ReleaseDownloadFile which invalidates the weak + // reference held by the DownloadFile and hence cuts off any pending + // callbacks. + DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL); + DVLOG(20) << __FUNCTION__ + << "() reason:" << DownloadInterruptReasonToString(reason); + // Postpone recognition of this error until after file name determination // has completed and the intermediate file has been renamed to simplify // resumption conditions. - if (current_path_.empty() || target_path_.empty()) + if (state_ == TARGET_PENDING_INTERNAL) { + received_bytes_ = bytes_so_far; + hash_state_ = std::move(secure_hash); + hash_.clear(); destination_error_ = reason; - else - Interrupt(reason); + return; + } + InterruptWithPartialState(bytes_so_far, std::move(secure_hash), reason); + UpdateObservers(); } -void DownloadItemImpl::DestinationCompleted(const std::string& final_hash) { +void DownloadItemImpl::DestinationCompleted( + int64_t total_bytes, + scoped_ptr<crypto::SecureHash> secure_hash) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + // If the download is in any other state we don't expect any + // DownloadDestinationObserver callbacks. An interruption or a cancellation + // results in a call to ReleaseDownloadFile which invalidates the weak + // reference held by the DownloadFile and hence cuts off any pending + // callbacks. + DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL); DVLOG(20) << __FUNCTION__ << " download=" << DebugString(true); - if (GetState() != IN_PROGRESS) - return; - OnAllDataSaved(final_hash); + + OnAllDataSaved(total_bytes, std::move(secure_hash)); MaybeCompleteDownload(); } @@ -1111,24 +1132,79 @@ void DownloadItemImpl::Init(bool active, // We're starting the download. void DownloadItemImpl::Start( scoped_ptr<DownloadFile> file, - scoped_ptr<DownloadRequestHandleInterface> req_handle) { + scoped_ptr<DownloadRequestHandleInterface> req_handle, + const DownloadCreateInfo& new_create_info) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(!download_file_.get()); - DCHECK(file.get()); - DCHECK(req_handle.get()); + DVLOG(20) << __FUNCTION__ << "() this=" << DebugString(true); download_file_ = std::move(file); request_handle_ = std::move(req_handle); + destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE; - if (GetState() == CANCELLED) { + if (state_ == CANCELLED_INTERNAL) { // The download was in the process of resuming when it was cancelled. Don't // proceed. ReleaseDownloadFile(true); - request_handle_->CancelRequest(); + if (request_handle_) + request_handle_->CancelRequest(); + return; + } + + // The state could be one of the following: + // + // INITIAL_INTERNAL: A normal download attempt. + // + // RESUMING_INTERNAL: A resumption attempt. May or may not have been + // successful. + DCHECK(state_ == INITIAL_INTERNAL || state_ == RESUMING_INTERNAL); + + // If the state_ is INITIAL_INTERNAL, then the target path must be empty. + DCHECK(state_ != INITIAL_INTERNAL || target_path_.empty()); + + // If a resumption attempted failed, or if the download was DOA, then the + // download should go back to being interrupted. + if (new_create_info.result != DOWNLOAD_INTERRUPT_REASON_NONE) { + DCHECK(!download_file_.get()); + + // Download requests that are interrupted by Start() should result in a + // DownloadCreateInfo with an intact DownloadSaveInfo. + DCHECK(new_create_info.save_info); + + int64_t offset = new_create_info.save_info->offset; + scoped_ptr<crypto::SecureHash> hash_state = + make_scoped_ptr(new_create_info.save_info->hash_state + ? new_create_info.save_info->hash_state->Clone() + : nullptr); + + // Interrupted downloads also need a target path. + if (target_path_.empty()) { + received_bytes_ = offset; + hash_state_ = std::move(hash_state); + hash_.clear(); + destination_error_ = new_create_info.result; + TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL); + DetermineDownloadTarget(); + return; + } + + // Otherwise, this was a resumption attempt which ended with an + // interruption. Continue with current target path. + TransitionTo(TARGET_RESOLVED_INTERNAL); + InterruptWithPartialState( + offset, std::move(hash_state), new_create_info.result); + UpdateObservers(); return; } - TransitionTo(IN_PROGRESS_INTERNAL, UPDATE_OBSERVERS); + // Successful download start. + DCHECK(download_file_.get()); + DCHECK(request_handle_.get()); + + if (state_ == RESUMING_INTERNAL) + UpdateValidatorsOnResumption(new_create_info); + + TransitionTo(TARGET_PENDING_INTERNAL); BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, @@ -1142,32 +1218,41 @@ void DownloadItemImpl::Start( void DownloadItemImpl::OnDownloadFileInitialized( DownloadInterruptReason result) { DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK_EQ(state_, TARGET_PENDING_INTERNAL); + DVLOG(20) << __FUNCTION__ + << "() result:" << DownloadInterruptReasonToString(result); if (result != DOWNLOAD_INTERRUPT_REASON_NONE) { - Interrupt(result); - // TODO(rdsmith/asanka): Arguably we should show this in the UI, but - // it's not at all clear what to show--we haven't done filename - // determination, so we don't know what name to display. OTOH, - // the failure mode of not showing the DI if the file initialization - // fails isn't a good one. Can we hack up a name based on the - // URLRequest? We'll need to make sure that initialization happens - // properly. Possibly the right thing is to have the UI handle - // this case specially. - return; + // Whoops. That didn't work. Proceed as an interrupted download, but reset + // the partial state. Currently, the partial stub cannot be recovered if the + // download file initialization fails. + received_bytes_ = 0; + hash_state_.reset(); + hash_.clear(); + destination_error_ = result; + TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL); } + DetermineDownloadTarget(); +} + +void DownloadItemImpl::DetermineDownloadTarget() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DVLOG(20) << __FUNCTION__ << "() " << DebugString(true); + delegate_->DetermineDownloadTarget( this, base::Bind(&DownloadItemImpl::OnDownloadTargetDetermined, weak_ptr_factory_.GetWeakPtr())); } -// Called by delegate_ when the download target path has been -// determined. +// Called by delegate_ when the download target path has been determined. void DownloadItemImpl::OnDownloadTargetDetermined( const base::FilePath& target_path, TargetDisposition disposition, DownloadDangerType danger_type, const base::FilePath& intermediate_path) { DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK(state_ == TARGET_PENDING_INTERNAL || + state_ == INTERRUPTED_TARGET_PENDING_INTERNAL); // If the |target_path| is empty, then we consider this download to be // canceled. @@ -1176,21 +1261,24 @@ void DownloadItemImpl::OnDownloadTargetDetermined( return; } - // TODO(rdsmith,asanka): We are ignoring the possibility that the download - // has been interrupted at this point until we finish the intermediate - // rename and set the full path. That's dangerous, because we might race - // with resumption, either manual (because the interrupt is visible to the - // UI) or automatic. If we keep the "ignore an error on download until file - // name determination complete" semantics, we need to make sure that the - // error is kept completely invisible until that point. - - DVLOG(20) << __FUNCTION__ << " " << target_path.value() << " " << disposition - << " " << danger_type << " " << DebugString(true); + DVLOG(20) << __FUNCTION__ << "() target_path:" << target_path.value() + << " disposition:" << disposition << " danger_type:" << danger_type + << " this:" << DebugString(true); target_path_ = target_path; target_disposition_ = disposition; SetDangerType(danger_type); + // This was an interrupted download that was looking for a filename. Now that + // it has one, transition to interrupted. + if (state_ == INTERRUPTED_TARGET_PENDING_INTERNAL) { + InterruptWithPartialState( + received_bytes_, std::move(hash_state_), destination_error_); + destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE; + UpdateObservers(); + return; + } + // We want the intermediate and target paths to refer to the same directory so // that they are both on the same device and subject to same // space/permission/availability constraints. @@ -1231,27 +1319,38 @@ void DownloadItemImpl::OnDownloadRenamedToIntermediateName( DownloadInterruptReason reason, const base::FilePath& full_path) { DCHECK_CURRENTLY_ON(BrowserThread::UI); + DCHECK_EQ(state_, TARGET_PENDING_INTERNAL); DVLOG(20) << __FUNCTION__ << " download=" << DebugString(true); + TransitionTo(TARGET_RESOLVED_INTERNAL); + + // If the intermediate rename fails while there's also a destination_error_, + // then the former is considered the critical error since it requires + // discarding the partial state. + if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) { + // TODO(asanka): Even though the rename failed, it may still be possible to + // recover the partial state from the 'before' name. + InterruptAndDiscardPartialState(reason); + UpdateObservers(); + return; + } + if (DOWNLOAD_INTERRUPT_REASON_NONE != destination_error_) { - // Process destination error. If both |reason| and |destination_error_| - // refer to actual errors, we want to use the |destination_error_| as the - // argument to the Interrupt() routine, as it happened first. - if (reason == DOWNLOAD_INTERRUPT_REASON_NONE) - SetFullPath(full_path); - Interrupt(destination_error_); - destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE; - } else if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) { - Interrupt(reason); - // All file errors result in file deletion above; no need to cleanup. The - // current_path_ should be empty. Resuming this download will force a - // restart and a re-doing of filename determination. - DCHECK(current_path_.empty()); - } else { SetFullPath(full_path); + InterruptWithPartialState( + received_bytes_, std::move(hash_state_), destination_error_); + destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE; UpdateObservers(); - MaybeCompleteDownload(); + return; } + + SetFullPath(full_path); + TransitionTo(IN_PROGRESS_INTERNAL); + // TODO(asanka): Calling UpdateObservers() prior to MaybeCompleteDownload() is + // not safe. The download could be in an underminate state after invoking + // observers. http://crbug.com/586610 + UpdateObservers(); + MaybeCompleteDownload(); } // When SavePackage downloads MHTML to GData (see @@ -1272,12 +1371,8 @@ void DownloadItemImpl::MaybeCompleteDownload() { weak_ptr_factory_.GetWeakPtr()))) return; - // TODO(rdsmith): DCHECK that we only pass through this point - // once per download. The natural way to do this is by a state - // transition on the DownloadItem. - - // Confirm we're in the proper set of states to be here; - // have all data, have a history handle, (validated or safe). + // Confirm we're in the proper set of states to be here; have all data, have a + // history handle, (validated or safe). DCHECK_EQ(IN_PROGRESS_INTERNAL, state_); DCHECK(!IsDangerous()); DCHECK(all_data_saved_); @@ -1301,9 +1396,8 @@ void DownloadItemImpl::OnDownloadCompleting() { // TODO(rdsmith/benjhayden): Remove as part of SavePackage integration. if (is_save_package_download_) { // Avoid doing anything on the file thread; there's nothing we control - // there. - // Strictly speaking, this skips giving the embedder a chance to open - // the download. But on a save package download, there's no real + // there. Strictly speaking, this skips giving the embedder a chance to + // open the download. But on a save package download, there's no real // concept of opening. Completed(); return; @@ -1316,10 +1410,15 @@ void DownloadItemImpl::OnDownloadCompleting() { base::Bind(&DownloadItemImpl::OnDownloadRenamedToFinalName, weak_ptr_factory_.GetWeakPtr()); BrowserThread::PostTask( - BrowserThread::FILE, FROM_HERE, + BrowserThread::FILE, + FROM_HERE, base::Bind(&DownloadFile::RenameAndAnnotate, base::Unretained(download_file_.get()), - GetTargetFilePath(), callback)); + GetTargetFilePath(), + delegate_->GetApplicationClientIdForFileScanning(), + GetURL(), + GetReferrerUrl(), + callback)); } void DownloadItemImpl::OnDownloadRenamedToFinalName( @@ -1339,11 +1438,11 @@ void DownloadItemImpl::OnDownloadRenamedToFinalName( << " " << DebugString(false); if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) { - Interrupt(reason); - - // All file errors should have resulted in in file deletion above. On - // resumption we will need to re-do filename determination. - DCHECK(current_path_.empty()); + // Failure to perform the final rename is considered fatal. TODO(asanka): It + // may not be, in which case we should figure out whether we can recover the + // state. + InterruptAndDiscardPartialState(reason); + UpdateObservers(); return; } @@ -1356,14 +1455,14 @@ void DownloadItemImpl::OnDownloadRenamedToFinalName( } // Complete the download and release the DownloadFile. - DCHECK(download_file_.get()); + DCHECK(download_file_); ReleaseDownloadFile(false); // We're not completely done with the download item yet, but at this // point we're committed to complete the download. Cancels (or Interrupts, // though it's not clear how they could happen) after this point will be // ignored. - TransitionTo(COMPLETING_INTERNAL, DONT_UPDATE_OBSERVERS); + TransitionTo(COMPLETING_INTERNAL); if (delegate_->ShouldOpenDownload( this, base::Bind(&DownloadItemImpl::DelayedDownloadOpened, @@ -1389,7 +1488,7 @@ void DownloadItemImpl::Completed() { DCHECK(all_data_saved_); end_time_ = base::Time::Now(); - TransitionTo(COMPLETE_INTERNAL, UPDATE_OBSERVERS); + TransitionTo(COMPLETE_INTERNAL); RecordDownloadCompleted(start_tick_, received_bytes_); if (auto_opened_) { @@ -1404,32 +1503,28 @@ void DownloadItemImpl::Completed() { OpenDownload(); auto_opened_ = true; - UpdateObservers(); } -} - -void DownloadItemImpl::OnResumeRequestStarted( - DownloadItem* item, - DownloadInterruptReason interrupt_reason) { - // If |item| is not NULL, then Start() has been called already, and nothing - // more needs to be done here. - if (item) { - DCHECK_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); - DCHECK_EQ(static_cast<DownloadItem*>(this), item); - return; - } - // Otherwise, the request failed without passing through - // DownloadResourceHandler::OnResponseStarted. - DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); - Interrupt(interrupt_reason); + UpdateObservers(); } // **** End of Download progression cascade -// An error occurred somewhere. -void DownloadItemImpl::Interrupt(DownloadInterruptReason reason) { +void DownloadItemImpl::InterruptAndDiscardPartialState( + DownloadInterruptReason reason) { + InterruptWithPartialState(0, scoped_ptr<crypto::SecureHash>(), reason); +} + +void DownloadItemImpl::InterruptWithPartialState( + int64_t bytes_so_far, + scoped_ptr<crypto::SecureHash> hash_state, + DownloadInterruptReason reason) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, reason); + DVLOG(20) << __FUNCTION__ + << "() reason:" << DownloadInterruptReasonToString(reason) + << " bytes_so_far:" << bytes_so_far + << " hash_state:" << (hash_state ? "Valid" : "Invalid") + << " this=" << DebugString(true); // Somewhat counter-intuitively, it is possible for us to receive an // interrupt after we've already been interrupted. The generation of @@ -1439,56 +1534,130 @@ void DownloadItemImpl::Interrupt(DownloadInterruptReason reason) { // respectively), and since we choose not to keep state on the File thread, // this is the place where the races collide. It's also possible for // interrupts to race with cancels. + switch (state_) { + case CANCELLED_INTERNAL: + // If the download is already cancelled, then there's no point in + // transitioning out to interrupted. + case COMPLETING_INTERNAL: + case COMPLETE_INTERNAL: + // Already complete. + return; - // Whatever happens, the first one to hit the UI thread wins. - if (state_ != IN_PROGRESS_INTERNAL && state_ != RESUMING_INTERNAL) - return; + case INITIAL_INTERNAL: + case MAX_DOWNLOAD_INTERNAL_STATE: + NOTREACHED(); + return; - last_reason_ = reason; + case INTERRUPTED_TARGET_PENDING_INTERNAL: + case IN_PROGRESS_INTERNAL: + case TARGET_PENDING_INTERNAL: + case TARGET_RESOLVED_INTERNAL: + // last_reason_ needs to be set for GetResumeMode() to work. + last_reason_ = reason; + + if (download_file_) { + ResumeMode resume_mode = GetResumeMode(); + ReleaseDownloadFile(resume_mode != RESUME_MODE_IMMEDIATE_CONTINUE && + resume_mode != RESUME_MODE_USER_CONTINUE); + } + break; - ResumeMode resume_mode = GetResumeMode(); + case RESUMING_INTERNAL: + case INTERRUPTED_INTERNAL: + DCHECK(!download_file_); + // The first non-cancel interrupt reason wins in cases where multiple + // things go wrong. + if (reason != DOWNLOAD_INTERRUPT_REASON_USER_CANCELED && + reason != DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN) + return; - if (state_ == IN_PROGRESS_INTERNAL) { - // Cancel (delete file) if: - // 1) we're going to restart. - // 2) Resumption isn't possible (download was cancelled or blocked due to - // security restrictions). - // 3) Resumption isn't enabled. - // No point in leaving data around we aren't going to use. - ReleaseDownloadFile(resume_mode == RESUME_MODE_IMMEDIATE_RESTART || - resume_mode == RESUME_MODE_USER_RESTART || - resume_mode == RESUME_MODE_INVALID || - !IsDownloadResumptionEnabled()); + last_reason_ = reason; + if (!current_path_.empty()) { + // There is no download file and this is transitioning from INTERRUPTED + // to CANCELLED. The intermediate file is no longer usable, and should + // be deleted. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(base::IgnoreResult(&DeleteDownloadedFile), + current_path_)); + current_path_.clear(); + } + break; + } - // Cancel the originating URL request. - request_handle_->CancelRequest(); + // Reset all data saved, as even if we did save all the data we're going to go + // through another round of downloading when we resume. There's a potential + // problem here in the abstract, as if we did download all the data and then + // run into a continuable error, on resumption we won't download any more + // data. However, a) there are currently no continuable errors that can occur + // after we download all the data, and b) if there were, that would probably + // simply result in a null range request, which would generate a + // DestinationCompleted() notification from the DownloadFile, which would + // behave properly with setting all_data_saved_ to false here. + all_data_saved_ = false; + + if (current_path_.empty()) { + hash_state_.reset(); + hash_.clear(); + received_bytes_ = 0; } else { - DCHECK(!download_file_.get()); + UpdateProgress(bytes_so_far, 0); + SetHashState(std::move(hash_state)); } - // Reset all data saved, as even if we did save all the data we're going - // to go through another round of downloading when we resume. - // There's a potential problem here in the abstract, as if we did download - // all the data and then run into a continuable error, on resumption we - // won't download any more data. However, a) there are currently no - // continuable errors that can occur after we download all the data, and - // b) if there were, that would probably simply result in a null range - // request, which would generate a DestinationCompleted() notification - // from the DownloadFile, which would behave properly with setting - // all_data_saved_ to false here. - all_data_saved_ = false; + if (request_handle_) + request_handle_->CancelRequest(); + + if (reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED || + reason == DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN) { + if (IsDangerous()) { + RecordDangerousDownloadDiscard( + reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED + ? DOWNLOAD_DISCARD_DUE_TO_USER_ACTION + : DOWNLOAD_DISCARD_DUE_TO_SHUTDOWN, + GetDangerType(), GetTargetFilePath()); + } + + RecordDownloadCount(CANCELLED_COUNT); + TransitionTo(CANCELLED_INTERNAL); + return; + } - TransitionTo(INTERRUPTED_INTERNAL, DONT_UPDATE_OBSERVERS); RecordDownloadInterrupted(reason, received_bytes_, total_bytes_); if (!GetWebContents()) RecordDownloadCount(INTERRUPTED_WITHOUT_WEBCONTENTS); + TransitionTo(INTERRUPTED_INTERNAL); AutoResumeIfValid(); - UpdateObservers(); +} + +void DownloadItemImpl::UpdateProgress(int64_t bytes_so_far, + int64_t bytes_per_sec) { + received_bytes_ = bytes_so_far; + bytes_per_sec_ = bytes_per_sec; + + // If we've received more data than we were expecting (bad server info?), + // revert to 'unknown size mode'. + if (received_bytes_ > total_bytes_) + total_bytes_ = 0; +} + +void DownloadItemImpl::SetHashState(scoped_ptr<crypto::SecureHash> hash_state) { + hash_state_ = std::move(hash_state); + if (!hash_state_) { + hash_.clear(); + return; + } + + scoped_ptr<crypto::SecureHash> clone_of_hash_state(hash_state_->Clone()); + std::vector<char> hash_value(clone_of_hash_state->GetHashLength()); + clone_of_hash_state->Finish(&hash_value.front(), hash_value.size()); + hash_.assign(hash_value.begin(), hash_value.end()); } void DownloadItemImpl::ReleaseDownloadFile(bool destroy_file) { DCHECK_CURRENTLY_ON(BrowserThread::UI); + DVLOG(20) << __FUNCTION__ << "() destroy_file:" << destroy_file; if (destroy_file) { BrowserThread::PostTask( @@ -1514,6 +1683,11 @@ void DownloadItemImpl::ReleaseDownloadFile(bool destroy_file) { bool DownloadItemImpl::IsDownloadReadyForCompletion( const base::Closure& state_change_notification) { + // If the download hasn't progressed to the IN_PROGRESS state, then it's not + // ready for completion. + if (state_ != IN_PROGRESS_INTERNAL) + return false; + // If we don't have all the data, the download is not ready for // completion. if (!AllDataSaved()) @@ -1524,20 +1698,13 @@ bool DownloadItemImpl::IsDownloadReadyForCompletion( if (IsDangerous()) return false; - // If the download isn't active (e.g. has been cancelled) it's not - // ready for completion. - if (state_ != IN_PROGRESS_INTERNAL) - return false; - - // If the target filename hasn't been determined, then it's not ready for - // completion. This is checked in ReadyForDownloadCompletionDone(). - if (GetTargetFilePath().empty()) - return false; - - // This is checked in NeedsRename(). Without this conditional, - // browser_tests:DownloadTest.DownloadMimeType fails the DCHECK. - if (target_path_.DirName() != current_path_.DirName()) - return false; + // Check for consistency before invoking delegate. Since there are no pending + // target determination calls and the download is in progress, both the target + // and current paths should be non-empty and they should point to the same + // directory. + DCHECK(!target_path_.empty()); + DCHECK(!current_path_.empty()); + DCHECK(target_path_.DirName() == current_path_.DirName()); // Give the delegate a chance to hold up a stop sign. It'll call // use back through the passed callback if it does and that state changes. @@ -1547,8 +1714,7 @@ bool DownloadItemImpl::IsDownloadReadyForCompletion( return true; } -void DownloadItemImpl::TransitionTo(DownloadInternalState new_state, - ShouldUpdateObservers notify_action) { +void DownloadItemImpl::TransitionTo(DownloadInternalState new_state) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (state_ == new_state) @@ -1557,49 +1723,89 @@ void DownloadItemImpl::TransitionTo(DownloadInternalState new_state, DownloadInternalState old_state = state_; state_ = new_state; + DCHECK(is_save_package_download_ + ? IsValidSavePackageStateTransition(old_state, new_state) + : IsValidStateTransition(old_state, new_state)) + << "Invalid state transition from:" << DebugDownloadStateString(old_state) + << " to:" << DebugDownloadStateString(new_state); + switch (state_) { + case INITIAL_INTERNAL: + NOTREACHED(); + break; + + case TARGET_PENDING_INTERNAL: + case TARGET_RESOLVED_INTERNAL: + case INTERRUPTED_TARGET_PENDING_INTERNAL: + break; + + case IN_PROGRESS_INTERNAL: + DCHECK(!current_path_.empty()) << "Current output path must be known."; + DCHECK(!target_path_.empty()) << "Target path must be known."; + DCHECK(current_path_.DirName() == target_path_.DirName()) + << "Current output directory must match target directory."; + DCHECK(download_file_) << "Output file must be owned by download item."; + DCHECK(request_handle_) << "Download source must be active."; + DCHECK(!is_paused_) << "At the time a download enters IN_PROGRESS state, " + "it must not be paused."; + break; + case COMPLETING_INTERNAL: + DCHECK(all_data_saved_) << "All data must be saved prior to completion."; + DCHECK(!download_file_) + << "Download file must be released prior to completion."; + DCHECK(!target_path_.empty()) << "Target path must be known."; + DCHECK(current_path_ == target_path_) + << "Current output path must match target path."; + bound_net_log_.AddEvent( net::NetLog::TYPE_DOWNLOAD_ITEM_COMPLETING, base::Bind(&ItemCompletingNetLogCallback, received_bytes_, &hash_)); break; + case COMPLETE_INTERNAL: bound_net_log_.AddEvent( net::NetLog::TYPE_DOWNLOAD_ITEM_FINISHED, base::Bind(&ItemFinishedNetLogCallback, auto_opened_)); break; + case INTERRUPTED_INTERNAL: bound_net_log_.AddEvent( net::NetLog::TYPE_DOWNLOAD_ITEM_INTERRUPTED, - base::Bind(&ItemInterruptedNetLogCallback, last_reason_, - received_bytes_, &hash_state_)); + base::Bind( + &ItemInterruptedNetLogCallback, last_reason_, received_bytes_)); break; - case IN_PROGRESS_INTERNAL: - if (old_state == INTERRUPTED_INTERNAL) { - bound_net_log_.AddEvent( - net::NetLog::TYPE_DOWNLOAD_ITEM_RESUMED, - base::Bind(&ItemResumingNetLogCallback, - false, last_reason_, received_bytes_, &hash_state_)); - } + + case RESUMING_INTERNAL: + bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_ITEM_RESUMED, + base::Bind(&ItemResumingNetLogCallback, + false, + last_reason_, + received_bytes_)); break; + case CANCELLED_INTERNAL: bound_net_log_.AddEvent( net::NetLog::TYPE_DOWNLOAD_ITEM_CANCELED, - base::Bind(&ItemCanceledNetLogCallback, received_bytes_, - &hash_state_)); + base::Bind(&ItemCanceledNetLogCallback, received_bytes_)); break; - default: + + case MAX_DOWNLOAD_INTERNAL_STATE: + NOTREACHED(); break; } - DVLOG(20) << " " << __FUNCTION__ << "()" << " this = " << DebugString(true) - << " " << InternalToExternalState(old_state) - << " " << InternalToExternalState(state_); + DVLOG(20) << " " << __FUNCTION__ << "()" + << " from:" << DebugDownloadStateString(old_state) + << " to:" << DebugDownloadStateString(state_) + << " this = " << DebugString(true); + bool is_done = + (state_ == COMPLETE_INTERNAL || state_ == INTERRUPTED_INTERNAL || + state_ == RESUMING_INTERNAL || state_ == CANCELLED_INTERNAL); + bool was_done = + (old_state == COMPLETE_INTERNAL || old_state == INTERRUPTED_INTERNAL || + old_state == RESUMING_INTERNAL || old_state == CANCELLED_INTERNAL); - bool is_done = (state_ != IN_PROGRESS_INTERNAL && - state_ != COMPLETING_INTERNAL); - bool was_done = (old_state != IN_PROGRESS_INTERNAL && - old_state != COMPLETING_INTERNAL); // Termination if (is_done && !was_done) bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE); @@ -1612,9 +1818,6 @@ void DownloadItemImpl::TransitionTo(DownloadInternalState new_state, this, SRC_ACTIVE_DOWNLOAD, &file_name)); } - - if (notify_action == UPDATE_OBSERVERS) - UpdateObservers(); } void DownloadItemImpl::SetDangerType(DownloadDangerType danger_type) { @@ -1676,45 +1879,52 @@ void DownloadItemImpl::ResumeInterruptedDownload() { if (state_ != INTERRUPTED_INTERNAL) return; + // We are starting a new request. Shake off all pending operations. + DCHECK(!download_file_); + weak_ptr_factory_.InvalidateWeakPtrs(); + // Reset the appropriate state if restarting. ResumeMode mode = GetResumeMode(); if (mode == RESUME_MODE_IMMEDIATE_RESTART || mode == RESUME_MODE_USER_RESTART) { received_bytes_ = 0; - hash_state_ = ""; - last_modified_time_ = ""; - etag_ = ""; - } - - scoped_ptr<DownloadUrlParameters> download_params; - if (GetWebContents()) { - download_params = - DownloadUrlParameters::FromWebContents(GetWebContents(), GetURL()); - } else { - download_params = make_scoped_ptr(new DownloadUrlParameters( - GetURL(), -1, -1, -1, GetBrowserContext()->GetResourceContext())); + last_modified_time_.clear(); + etag_.clear(); + hash_.clear(); + hash_state_.reset(); } + // Avoid using the WebContents even if it's still around. Resumption requests + // are consistently routed through the no-renderer code paths so that the + // request will not be dropped if the WebContents (and by extension, the + // associated renderer) goes away before a response is received. + scoped_ptr<DownloadUrlParameters> download_params(new DownloadUrlParameters( + GetURL(), -1, -1, -1, GetBrowserContext()->GetResourceContext())); download_params->set_file_path(GetFullPath()); download_params->set_offset(GetReceivedBytes()); - download_params->set_hash_state(GetHashState()); download_params->set_last_modified(GetLastModifiedTime()); download_params->set_etag(GetETag()); - download_params->set_callback( - base::Bind(&DownloadItemImpl::OnResumeRequestStarted, - weak_ptr_factory_.GetWeakPtr())); + download_params->set_hash_of_partial_file(hash_); + download_params->set_hash_state(std::move(hash_state_)); + TransitionTo(RESUMING_INTERNAL); delegate_->ResumeInterruptedDownload(std::move(download_params), GetId()); // Just in case we were interrupted while paused. is_paused_ = false; - - TransitionTo(RESUMING_INTERNAL, DONT_UPDATE_OBSERVERS); } // static DownloadItem::DownloadState DownloadItemImpl::InternalToExternalState( DownloadInternalState internal_state) { switch (internal_state) { + case INITIAL_INTERNAL: + case TARGET_PENDING_INTERNAL: + case TARGET_RESOLVED_INTERNAL: + case INTERRUPTED_TARGET_PENDING_INTERNAL: + // TODO(asanka): Introduce an externally visible state to distinguish + // between the above states and IN_PROGRESS_INTERNAL. The latter (the + // state where the download is active and has a known target) is the state + // that most external users are interested in. case IN_PROGRESS_INTERNAL: return IN_PROGRESS; case COMPLETING_INTERNAL: @@ -1753,9 +1963,96 @@ DownloadItemImpl::ExternalToInternalState( return MAX_DOWNLOAD_INTERNAL_STATE; } +// static +bool DownloadItemImpl::IsValidSavePackageStateTransition( + DownloadInternalState from, + DownloadInternalState to) { +#if DCHECK_IS_ON() + switch (from) { + case INITIAL_INTERNAL: + case TARGET_PENDING_INTERNAL: + case INTERRUPTED_TARGET_PENDING_INTERNAL: + case TARGET_RESOLVED_INTERNAL: + case COMPLETING_INTERNAL: + case COMPLETE_INTERNAL: + case INTERRUPTED_INTERNAL: + case RESUMING_INTERNAL: + case CANCELLED_INTERNAL: + return false; + + case IN_PROGRESS_INTERNAL: + return to == CANCELLED_INTERNAL || to == COMPLETE_INTERNAL; + + case MAX_DOWNLOAD_INTERNAL_STATE: + NOTREACHED(); + } + return false; +#else + return true; +#endif +} + +// static +bool DownloadItemImpl::IsValidStateTransition(DownloadInternalState from, + DownloadInternalState to) { +#if DCHECK_IS_ON() + switch (from) { + case INITIAL_INTERNAL: + return to == TARGET_PENDING_INTERNAL || + to == INTERRUPTED_TARGET_PENDING_INTERNAL; + + case TARGET_PENDING_INTERNAL: + return to == INTERRUPTED_TARGET_PENDING_INTERNAL || + to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL; + + case INTERRUPTED_TARGET_PENDING_INTERNAL: + return to == INTERRUPTED_INTERNAL || to == CANCELLED_INTERNAL; + + case TARGET_RESOLVED_INTERNAL: + return to == IN_PROGRESS_INTERNAL || to == INTERRUPTED_INTERNAL || + to == CANCELLED_INTERNAL; + + case IN_PROGRESS_INTERNAL: + return to == COMPLETING_INTERNAL || to == CANCELLED_INTERNAL || + to == INTERRUPTED_INTERNAL; + + case COMPLETING_INTERNAL: + return to == COMPLETE_INTERNAL; + + case COMPLETE_INTERNAL: + return false; + + case INTERRUPTED_INTERNAL: + return to == RESUMING_INTERNAL || to == CANCELLED_INTERNAL; + + case RESUMING_INTERNAL: + return to == TARGET_PENDING_INTERNAL || + to == INTERRUPTED_TARGET_PENDING_INTERNAL || + to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL; + + case CANCELLED_INTERNAL: + return false; + + case MAX_DOWNLOAD_INTERNAL_STATE: + NOTREACHED(); + } + return false; +#else + return true; +#endif // DCHECK_IS_ON() +} + const char* DownloadItemImpl::DebugDownloadStateString( DownloadInternalState state) { switch (state) { + case INITIAL_INTERNAL: + return "INITIAL"; + case TARGET_PENDING_INTERNAL: + return "TARGET_PENDING"; + case INTERRUPTED_TARGET_PENDING_INTERNAL: + return "INTERRUPTED_TARGET_PENDING"; + case TARGET_RESOLVED_INTERNAL: + return "TARGET_RESOLVED"; case IN_PROGRESS_INTERNAL: return "IN_PROGRESS"; case COMPLETING_INTERNAL: diff --git a/chromium/content/browser/download/download_item_impl.h b/chromium/content/browser/download/download_item_impl.h index c527861287f..a6afdd86779 100644 --- a/chromium/content/browser/download/download_item_impl.h +++ b/chromium/content/browser/download/download_item_impl.h @@ -16,10 +16,10 @@ #include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "base/time/time.h" +#include "content/browser/download/download_destination_observer.h" #include "content/browser/download/download_net_log_parameters.h" #include "content/browser/download/download_request_handle.h" #include "content/common/content_export.h" -#include "content/public/browser/download_destination_observer.h" #include "content/public/browser/download_interrupt_reasons.h" #include "content/public/browser/download_item.h" #include "net/log/net_log.h" @@ -52,6 +52,7 @@ class CONTENT_EXPORT DownloadItemImpl // Constructing from persistent store: // |bound_net_log| is constructed externally for our use. DownloadItemImpl(DownloadItemImplDelegate* delegate, + const std::string& guid, uint32_t id, const base::FilePath& current_path, const base::FilePath& target_path, @@ -65,6 +66,7 @@ class CONTENT_EXPORT DownloadItemImpl const std::string& last_modified, int64_t received_bytes, int64_t total_bytes, + const std::string& hash, DownloadItem::DownloadState state, DownloadDangerType danger_type, DownloadInterruptReason interrupt_reason, @@ -73,6 +75,8 @@ class CONTENT_EXPORT DownloadItemImpl // Constructing for a regular download. // |bound_net_log| is constructed externally for our use. + // TODO(asanka): Get rid of the DownloadCreateInfo parameter since active + // downloads end up at Start() immediately after creation. DownloadItemImpl(DownloadItemImplDelegate* delegate, uint32_t id, const DownloadCreateInfo& info, @@ -103,6 +107,7 @@ class CONTENT_EXPORT DownloadItemImpl void OpenDownload() override; void ShowDownloadInShell() override; uint32_t GetId() const override; + const std::string& GetGuid() const override; DownloadState GetState() const override; DownloadInterruptReason GetLastReason() const override; bool IsPaused() const override; @@ -131,7 +136,6 @@ class CONTENT_EXPORT DownloadItemImpl base::FilePath GetFileNameToReportUser() const override; TargetDisposition GetTargetDisposition() const override; const std::string& GetHash() const override; - const std::string& GetHashState() const override; bool GetFileExternallyRemoved() const override; void DeleteFile(const base::Callback<void(bool)>& callback) override; bool IsDangerous() const override; @@ -167,18 +171,18 @@ class CONTENT_EXPORT DownloadItemImpl // INTERRUPTED state. virtual ResumeMode GetResumeMode() const; - // Notify the download item that new origin information is available due to a - // resumption request receiving a response. - virtual void MergeOriginInfoOnResume( - const DownloadCreateInfo& new_create_info); - // State transition operations on regular downloads -------------------------- // Start the download. // |download_file| is the associated file on the storage medium. // |req_handle| is the new request handle associated with the download. + // |new_create_info| is a DownloadCreateInfo containing the new response + // parameters. It may be different from the DownloadCreateInfo used to create + // the DownloadItem if Start() is being called in response for a download + // resumption request. virtual void Start(scoped_ptr<DownloadFile> download_file, - scoped_ptr<DownloadRequestHandleInterface> req_handle); + scoped_ptr<DownloadRequestHandleInterface> req_handle, + const DownloadCreateInfo& new_create_info); // Needed because of intertwining with DownloadManagerImpl ------------------- @@ -203,94 +207,171 @@ class CONTENT_EXPORT DownloadItemImpl // Called by SavePackage to set the total number of bytes on the item. virtual void SetTotalBytes(int64_t total_bytes); - virtual void OnAllDataSaved(const std::string& final_hash); + virtual void OnAllDataSaved(int64_t total_bytes, + scoped_ptr<crypto::SecureHash> hash_state); // Called by SavePackage to display progress when the DownloadItem // should be considered complete. virtual void MarkAsComplete(); // DownloadDestinationObserver - void DestinationUpdate(int64_t bytes_so_far, - int64_t bytes_per_sec, - const std::string& hash_state) override; - void DestinationError(DownloadInterruptReason reason) override; - void DestinationCompleted(const std::string& final_hash) override; + void DestinationUpdate(int64_t bytes_so_far, int64_t bytes_per_sec) override; + void DestinationError(DownloadInterruptReason reason, + int64_t bytes_so_far, + scoped_ptr<crypto::SecureHash> hash_state) override; + void DestinationCompleted(int64_t total_bytes, + scoped_ptr<crypto::SecureHash> hash_state) override; private: - // Fine grained states of a download. Note that active downloads are created - // in IN_PROGRESS_INTERNAL state. However, downloads creates via history can - // be created in COMPLETE_INTERNAL, CANCELLED_INTERNAL and - // INTERRUPTED_INTERNAL. - + // Fine grained states of a download. + // + // New downloads can be created in the following states: + // + // INITIAL_INTERNAL: All active new downloads. + // + // COMPLETE_INTERNAL: Downloads restored from persisted state. + // CANCELLED_INTERNAL: - do - + // INTERRUPTED_INTERNAL: - do - + // + // IN_PROGRESS_INTERNAL: SavePackage downloads. + // + // On debug builds, state transitions can be verified via + // IsValidStateTransition() and IsValidSavePackageStateTransition(). Allowed + // state transitions are described below, both for normal downloads and + // SavePackage downloads. enum DownloadInternalState { - // Includes both before and after file name determination, and paused - // downloads. - // TODO(rdsmith): Put in state variable for file name determination. - // Transitions from: - // <Initial creation> Active downloads are created in this state. - // RESUMING_INTERNAL - // Transitions to: - // COMPLETING_INTERNAL On final rename completion. - // CANCELLED_INTERNAL On cancel. - // INTERRUPTED_INTERNAL On interrupt. - // COMPLETE_INTERNAL On SavePackage download completion. + // Initial state. Regular downloads are created in this state until the + // Start() call is received. + // + // Transitions to (regular): + // TARGET_PENDING_INTERNAL: After a successful Start() call. + // INTERRUPTED_TARGET_PENDING_INTERNAL: After a failed Start() call. + // + // Transitions to (SavePackage): + // <n/a> SavePackage downloads never reach this state. + INITIAL_INTERNAL, + + // Embedder is in the process of determining the target of the download. + // Since the embedder is sensitive to state transitions during this time, + // any DestinationError/DestinationCompleted events are deferred until + // TARGET_RESOLVED_INTERNAL. + // + // Transitions to (regular): + // TARGET_RESOLVED_INTERNAL: Once the embedder invokes the callback. + // INTERRUPTED_TARGET_PENDING_INTERNAL: An error occurred prior to target + // determination. + // CANCELLED_INTERNAL: Cancelled. + // + // Transitions to (SavePackage): + // <n/a> SavePackage downloads never reach this state. + TARGET_PENDING_INTERNAL, + + // Embedder is in the process of determining the target of the download, and + // the download is in an interrupted state. The interrupted state is not + // exposed to the emedder until target determination is complete. + // + // Transitions to (regular): + // INTERRUPTED_INTERNAL: Once the target is determined, the download + // is marked as interrupted. + // CANCELLED_INTERNAL: Cancelled. + // + // Transitions to (SavePackage): + // <n/a> SavePackage downloads never reach this state. + INTERRUPTED_TARGET_PENDING_INTERNAL, + + // Embedder has completed target determination. It is now safe to resolve + // the download target as well as process deferred DestinationError events. + // This state is differs from TARGET_PENDING_INTERNAL due to it being + // allowed to transition to INTERRUPTED_INTERNAL, and it's different from + // IN_PROGRESS_INTERNAL in that entering this state doesn't require having + // a valid target. This state is transient (i.e. DownloadItemImpl will + // transition out of it before yielding execution). It's only purpose in + // life is to ensure the integrity of state transitions. + // + // Transitions to (regular): + // IN_PROGRESS_INTERNAL: Target successfully determined. The incoming + // data stream can now be written to the target. + // INTERRUPTED_INTERNAL: Either the target determination or one of the + // deferred signals indicated that the download + // should be interrupted. + // CANCELLED_INTERNAL: User cancelled the download or there was a + // deferred Cancel() call. + // + // Transitions to (SavePackage): + // <n/a> SavePackage downloads never reach this state. + TARGET_RESOLVED_INTERNAL, + + // Download target is known and the data can be transferred from our source + // to our sink. + // + // Transitions to (regular): + // COMPLETING_INTERNAL: On final rename completion. + // CANCELLED_INTERNAL: On cancel. + // INTERRUPTED_INTERNAL: On interrupt. + // + // Transitions to (SavePackage): + // COMPLETE_INTERNAL: On completion. + // CANCELLED_INTERNAL: On cancel. IN_PROGRESS_INTERNAL, // Between commit point (dispatch of download file release) and completed. // Embedder may be opening the file in this state. - // Transitions from: - // IN_PROGRESS_INTERNAL - // Transitions to: - // COMPLETE_INTERNAL On successful completion. + // + // Transitions to (regular): + // COMPLETE_INTERNAL: On successful completion. + // + // Transitions to (SavePackage): + // <n/a> SavePackage downloads never reach this state. COMPLETING_INTERNAL, // After embedder has had a chance to auto-open. User may now open // or auto-open based on extension. - // Transitions from: - // COMPLETING_INTERNAL - // IN_PROGRESS_INTERNAL SavePackage only. - // <Initial creation> Completed persisted downloads. - // Transitions to: - // <none> Terminal state. + // + // Transitions to (regular): + // <none> Terminal state. + // + // Transitions to (SavePackage): + // <none> Terminal state. COMPLETE_INTERNAL, - // User has cancelled the download. - // Transitions from: - // IN_PROGRESS_INTERNAL - // INTERRUPTED_INTERNAL - // RESUMING_INTERNAL - // <Initial creation> Canceleld persisted downloads. - // Transitions to: - // <none> Terminal state. - CANCELLED_INTERNAL, - // An error has interrupted the download. - // Transitions from: - // IN_PROGRESS_INTERNAL - // RESUMING_INTERNAL - // <Initial creation> Interrupted persisted downloads. - // Transitions to: - // RESUMING_INTERNAL On resumption. + // + // Transitions to (regular): + // RESUMING_INTERNAL: On resumption. + // CANCELLED_INTERNAL: On cancel. + // + // Transitions to (SavePackage): + // <n/a> SavePackage downloads never reach this state. INTERRUPTED_INTERNAL, // A request to resume this interrupted download is in progress. - // Transitions from: - // INTERRUPTED_INTERNAL - // Transitions to: - // IN_PROGRESS_INTERNAL Once a server response is received from a - // resumption. - // INTERRUPTED_INTERNAL If the resumption request fails. - // CANCELLED_INTERNAL On cancel. + // + // Transitions to (regular): + // TARGET_PENDING_INTERNAL: Once a server response is received from a + // resumption. + // INTERRUPTED_TARGET_PENDING_INTERNAL: A server response was received, + // but it indicated an error, and the download + // needs to go through target determination. + // TARGET_RESOLVED_INTERNAL: A resumption attempt received an error + // but it was not necessary to go through target + // determination. + // CANCELLED_INTERNAL: On cancel. + // + // Transitions to (SavePackage): + // <n/a> SavePackage downloads never reach this state. RESUMING_INTERNAL, - MAX_DOWNLOAD_INTERNAL_STATE, - }; + // User has cancelled the download. + // TODO(asanka): Merge interrupted and cancelled states. + // + // Transitions to (regular): + // <none> Terminal state. + // + // Transitions to (SavePackage): + // <none> Terminal state. + CANCELLED_INTERNAL, - // Used with TransitionTo() to indicate whether or not to call - // UpdateObservers() after the state transition. - enum ShouldUpdateObservers { - UPDATE_OBSERVERS, - DONT_UPDATE_OBSERVERS + MAX_DOWNLOAD_INTERNAL_STATE, }; // Normal progression of a download ------------------------------------------ @@ -305,6 +386,13 @@ class CONTENT_EXPORT DownloadItemImpl // this is. void Init(bool active, DownloadType download_type); + // Callback from file thread when we initialize the DownloadFile. + void OnDownloadFileInitialized(DownloadInterruptReason result); + + // Called to determine the target path. Will cause OnDownloadTargetDetermined + // to be called when the target information is available. + void DetermineDownloadTarget(); + // Called when the target path has been determined. |target_path| is the // suggested target path. |disposition| indicates how the target path should // be used (see TargetDisposition). |danger_type| is the danger level of @@ -316,9 +404,6 @@ class CONTENT_EXPORT DownloadItemImpl DownloadDangerType danger_type, const base::FilePath& intermediate_path); - // Callback from file thread when we initialize the DownloadFile. - void OnDownloadFileInitialized(DownloadInterruptReason result); - void OnDownloadRenamedToIntermediateName( DownloadInterruptReason reason, const base::FilePath& full_path); @@ -339,18 +424,29 @@ class CONTENT_EXPORT DownloadItemImpl // the download has been opened. void DelayedDownloadOpened(bool auto_opened); - // Called when the entire download operation (including renaming etc) + // Called when the entire download operation (including renaming etc.) // is completed. void Completed(); - // Callback invoked when the URLRequest for a download resumption has started. - void OnResumeRequestStarted(DownloadItem* item, - DownloadInterruptReason interrupt_reason); - // Helper routines ----------------------------------------------------------- - // Indicate that an error has occurred on the download. - void Interrupt(DownloadInterruptReason reason); + // Indicate that an error has occurred on the download. Discards partial + // state. The interrupted download will not be considered continuable, but may + // be restarted. + void InterruptAndDiscardPartialState(DownloadInterruptReason reason); + + // Indiates that an error has occurred on the download. The |bytes_so_far| and + // |hash_state| should correspond to the state of the DownloadFile. If the + // interrupt reason allows, this partial state may be allowed to continue the + // interrupted download upon resumption. + void InterruptWithPartialState(int64_t bytes_so_far, + scoped_ptr<crypto::SecureHash> hash_state, + DownloadInterruptReason reason); + + void UpdateProgress(int64_t bytes_so_far, int64_t bytes_per_sec); + + // Set |hash_| and |hash_state_| based on |hash_state|. + void SetHashState(scoped_ptr<crypto::SecureHash> hash_state); // Destroy the DownloadFile object. If |destroy_file| is true, the file is // destroyed with it. Otherwise, DownloadFile::Detach() is called before @@ -366,10 +462,9 @@ class CONTENT_EXPORT DownloadItemImpl // Call to transition state; all state transitions should go through this. // |notify_action| specifies whether or not to call UpdateObservers() after // the state transition. - void TransitionTo(DownloadInternalState new_state, - ShouldUpdateObservers notify_action); + void TransitionTo(DownloadInternalState new_state); - // Set the |danger_type_| and invoke obserers if necessary. + // Set the |danger_type_| and invoke observers if necessary. void SetDangerType(DownloadDangerType danger_type); void SetFullPath(const base::FilePath& new_path); @@ -378,6 +473,11 @@ class CONTENT_EXPORT DownloadItemImpl void ResumeInterruptedDownload(); + // Update origin information based on the response to a download resumption + // request. Should only be called if the resumption request was successful. + virtual void UpdateValidatorsOnResumption( + const DownloadCreateInfo& new_create_info); + static DownloadState InternalToExternalState( DownloadInternalState internal_state); static DownloadInternalState ExternalToInternalState( @@ -386,34 +486,34 @@ class CONTENT_EXPORT DownloadItemImpl // Debugging routines -------------------------------------------------------- static const char* DebugDownloadStateString(DownloadInternalState state); static const char* DebugResumeModeString(ResumeMode mode); + static bool IsValidSavePackageStateTransition(DownloadInternalState from, + DownloadInternalState to); + static bool IsValidStateTransition(DownloadInternalState from, + DownloadInternalState to); // Will be false for save package downloads retrieved from the history. // TODO(rdsmith): Replace with a generalized enum for "download source". - const bool is_save_package_download_; + const bool is_save_package_download_ = false; // The handle to the request information. Used for operations outside the // download system. scoped_ptr<DownloadRequestHandleInterface> request_handle_; - uint32_t download_id_; + std::string guid_; + + uint32_t download_id_ = kInvalidId; // Display name for the download. If this is empty, then the display name is // considered to be |target_path_.BaseName()|. base::FilePath display_name_; - // Full path to the downloaded or downloading file. This is the path to the - // physical file, if one exists. The final target path is specified by - // |target_path_|. |current_path_| can be empty if the in-progress path hasn't - // been determined. - base::FilePath current_path_; - // Target path of an in-progress download. We may be downloading to a // temporary or intermediate file (specified by |current_path_|. Once the // download completes, we will rename the file to |target_path_|. base::FilePath target_path_; // Whether the target should be overwritten, uniquified or prompted for. - TargetDisposition target_disposition_; + TargetDisposition target_disposition_ = TARGET_DISPOSITION_OVERWRITE; // The chain of redirects that leading up to and including the final URL. std::vector<GURL> url_chain_; @@ -437,10 +537,10 @@ class CONTENT_EXPORT DownloadItemImpl base::FilePath forced_file_path_; // Page transition that triggerred the download. - ui::PageTransition transition_type_; + ui::PageTransition transition_type_ = ui::PAGE_TRANSITION_LINK; // Whether the download was triggered with a user gesture. - bool has_user_gesture_; + bool has_user_gesture_ = false; // Information from the request. // Content-disposition field from the header. @@ -459,40 +559,19 @@ class CONTENT_EXPORT DownloadItemImpl std::string remote_address_; // Total bytes expected. - int64_t total_bytes_; - - // Current received bytes. - int64_t received_bytes_; - - // Current speed. Calculated by the DownloadFile. - int64_t bytes_per_sec_; - - // Sha256 hash of the content. This might be empty either because - // the download isn't done yet or because the hash isn't needed - // (ChromeDownloadManagerDelegate::GenerateFileHash() returned false). - std::string hash_; - - // A blob containing the state of the hash algorithm. Only valid while the - // download is in progress. - std::string hash_state_; - - // Server's time stamp for the file. - std::string last_modified_time_; - - // Server's ETAG for the file. - std::string etag_; + int64_t total_bytes_ = 0; // Last reason. - DownloadInterruptReason last_reason_; + DownloadInterruptReason last_reason_ = DOWNLOAD_INTERRUPT_REASON_NONE; // Start time for recording statistics. base::TimeTicks start_tick_; // The current state of this download. - DownloadInternalState state_; + DownloadInternalState state_ = INITIAL_INTERNAL; // Current danger type for the download. - DownloadDangerType danger_type_; + DownloadDangerType danger_type_ = DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS; // The views of this item in the download shelf and download contents. base::ObserverList<Observer> observers_; @@ -504,44 +583,40 @@ class CONTENT_EXPORT DownloadItemImpl base::Time end_time_; // Our delegate. - DownloadItemImplDelegate* delegate_; + DownloadItemImplDelegate* delegate_ = nullptr; // In progress downloads may be paused by the user, we note it here. - bool is_paused_; - - // The number of times this download has been resumed automatically. - int auto_resume_count_; + bool is_paused_ = false; // A flag for indicating if the download should be opened at completion. - bool open_when_complete_; + bool open_when_complete_ = false; // A flag for indicating if the downloaded file is externally removed. - bool file_externally_removed_; + bool file_externally_removed_ = false; // True if the download was auto-opened. We set this rather than using // an observer as it's frequently possible for the download to be auto opened // before the observer is added. - bool auto_opened_; + bool auto_opened_ = false; // True if the item was downloaded temporarily. - bool is_temporary_; - - // True if we've saved all the data for the download. - bool all_data_saved_; - - // Error return from DestinationError. Stored separately from - // last_reason_ so that we can avoid handling destination errors until - // after file name determination has occurred. - DownloadInterruptReason destination_error_; + bool is_temporary_ = false; // Did the user open the item either directly or indirectly (such as by // setting always open files of this type)? The shelf also sets this field // when the user closes the shelf before the item has been opened but should // be treated as though the user opened it. - bool opened_; + bool opened_ = false; // Did the delegate delay calling Complete on this download? - bool delegate_delayed_complete_; + bool delegate_delayed_complete_ = false; + + // Error return from DestinationError. Stored separately from + // last_reason_ so that we can avoid handling destination errors until + // after file name determination has occurred. + DownloadInterruptReason destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE; + + // The following fields describe the current state of the download file. // DownloadFile associated with this download. Note that this // pointer may only be used or destroyed on the FILE thread. @@ -549,6 +624,47 @@ class CONTENT_EXPORT DownloadItemImpl // the IN_PROGRESS state. scoped_ptr<DownloadFile> download_file_; + // Full path to the downloaded or downloading file. This is the path to the + // physical file, if one exists. The final target path is specified by + // |target_path_|. |current_path_| can be empty if the in-progress path hasn't + // been determined. + base::FilePath current_path_; + + // Current received bytes. + int64_t received_bytes_ = 0; + + // Current speed. Calculated by the DownloadFile. + int64_t bytes_per_sec_ = 0; + + // True if we've saved all the data for the download. If true, then the file + // at |current_path_| contains |received_bytes_|, which constitute the + // entirety of what we expect to save there. A digest of its contents can be + // found at |hash_|. + bool all_data_saved_ = false; + + // The number of times this download has been resumed automatically. Will be + // reset to 0 if a resumption is performed in response to a Resume() call. + int auto_resume_count_ = 0; + + // SHA256 hash of the possibly partial content. The hash is updated each time + // the download is interrupted, and when the all the data has been + // transferred. |hash_| contains the raw binary hash and is not hex encoded. + // + // While the download is in progress, and while resuming, |hash_| will be + // empty. + std::string hash_; + + // In the event of an interruption, the DownloadDestinationObserver interface + // exposes the partial hash state. This state can be held by the download item + // in case it's needed for resumption. + scoped_ptr<crypto::SecureHash> hash_state_; + + // Contents of the Last-Modified header for the most recent server response. + std::string last_modified_time_; + + // Server's ETAG for the file. + std::string etag_; + // Net log to use for this download. const net::BoundNetLog bound_net_log_; diff --git a/chromium/content/browser/download/download_item_impl_delegate.cc b/chromium/content/browser/download/download_item_impl_delegate.cc index c1ed48cd124..be7432599e1 100644 --- a/chromium/content/browser/download/download_item_impl_delegate.cc +++ b/chromium/content/browser/download/download_item_impl_delegate.cc @@ -56,6 +56,11 @@ bool DownloadItemImplDelegate::ShouldOpenFileBasedOnExtension( void DownloadItemImplDelegate::CheckForFileRemoval( DownloadItemImpl* download_item) {} +std::string DownloadItemImplDelegate::GetApplicationClientIdForFileScanning() + const { + return std::string(); +} + void DownloadItemImplDelegate::ResumeInterruptedDownload( scoped_ptr<DownloadUrlParameters> params, uint32_t id) {} diff --git a/chromium/content/browser/download/download_item_impl_delegate.h b/chromium/content/browser/download/download_item_impl_delegate.h index 1925963f0a2..8dd9d8cb740 100644 --- a/chromium/content/browser/download/download_item_impl_delegate.h +++ b/chromium/content/browser/download/download_item_impl_delegate.h @@ -69,6 +69,11 @@ class CONTENT_EXPORT DownloadItemImplDelegate { // to OnDownloadedFileRemoved(). virtual void CheckForFileRemoval(DownloadItemImpl* download_item); + // Return a GUID string used for identifying the application to the system AV + // function for scanning downloaded files. If an empty or invalid GUID string + // is returned, no client identification will be given to the AV function. + virtual std::string GetApplicationClientIdForFileScanning() const; + // Called when an interrupted download is resumed. virtual void ResumeInterruptedDownload( scoped_ptr<content::DownloadUrlParameters> params, diff --git a/chromium/content/browser/download/download_item_impl_unittest.cc b/chromium/content/browser/download/download_item_impl_unittest.cc index 0afad1bfca2..45c822cfc55 100644 --- a/chromium/content/browser/download/download_item_impl_unittest.cc +++ b/chromium/content/browser/download/download_item_impl_unittest.cc @@ -5,39 +5,54 @@ #include "content/browser/download/download_item_impl.h" #include <stdint.h> + +#include <iterator> +#include <queue> #include <utility> #include "base/callback.h" #include "base/feature_list.h" #include "base/message_loop/message_loop.h" +#include "base/run_loop.h" #include "base/stl_util.h" #include "base/threading/thread.h" #include "content/browser/byte_stream.h" #include "content/browser/download/download_create_info.h" +#include "content/browser/download/download_destination_observer.h" #include "content/browser/download/download_file_factory.h" #include "content/browser/download/download_item_impl_delegate.h" #include "content/browser/download/download_request_handle.h" #include "content/browser/download/mock_download_file.h" -#include "content/public/browser/download_destination_observer.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/download_interrupt_reasons.h" #include "content/public/browser/download_url_parameters.h" #include "content/public/common/content_features.h" #include "content/public/test/mock_download_item.h" +#include "content/public/test/mock_download_item.h" #include "content/public/test/test_browser_context.h" -#include "content/public/test/test_browser_thread.h" +#include "content/public/test/test_browser_context.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "content/public/test/web_contents_tester.h" +#include "crypto/secure_hash.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -using ::testing::_; +using ::testing::DoAll; using ::testing::NiceMock; using ::testing::Property; using ::testing::Return; +using ::testing::ReturnRefOfCopy; using ::testing::SaveArg; using ::testing::StrictMock; +using ::testing::WithArg; +using ::testing::_; const int kDownloadChunkSize = 1000; const int kDownloadSpeed = 1000; -const base::FilePath::CharType kDummyPath[] = FILE_PATH_LITERAL("/testpath"); +const base::FilePath::CharType kDummyTargetPath[] = + FILE_PATH_LITERAL("/testpath"); +const base::FilePath::CharType kDummyIntermediatePath[] = + FILE_PATH_LITERAL("/testpathx"); namespace content { @@ -66,7 +81,6 @@ class MockDelegate : public DownloadItemImplDelegate { void(DownloadUrlParameters* params, uint32_t id)); MOCK_CONST_METHOD0(GetBrowserContext, BrowserContext*()); - MOCK_METHOD1(UpdatePersistence, void(DownloadItemImpl*)); MOCK_METHOD1(DownloadOpened, void(DownloadItemImpl*)); MOCK_METHOD1(DownloadRemoved, void(DownloadItemImpl*)); MOCK_CONST_METHOD1(AssertStateConsistent, void(DownloadItemImpl*)); @@ -128,8 +142,8 @@ class TestDownloadItemObserver : public DownloadItem::Observer { private: void OnDownloadRemoved(DownloadItem* download) override { - DVLOG(20) << " " << __FUNCTION__ - << " download = " << download->DebugString(false); + SCOPED_TRACE(::testing::Message() << " " << __FUNCTION__ << " download = " + << download->DebugString(false)); removed_ = true; } @@ -159,7 +173,7 @@ class TestDownloadItemObserver : public DownloadItem::Observer { << " download = " << download->DebugString(false); destroyed_ = true; item_->RemoveObserver(this); - item_ = NULL; + item_ = nullptr; } DownloadItem* item_; @@ -173,54 +187,82 @@ class TestDownloadItemObserver : public DownloadItem::Observer { // Schedules a task to invoke the RenameCompletionCallback with |new_path| on // the UI thread. Should only be used as the action for -// MockDownloadFile::Rename as follows: -// EXPECT_CALL(download_file, Rename*(_,_)) -// .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, -// new_path)); -ACTION_P2(ScheduleRenameCallback, interrupt_reason, new_path) { +// MockDownloadFile::RenameAndUniquify as follows: +// EXPECT_CALL(download_file, RenameAndUniquify(_,_)) +// .WillOnce(ScheduleRenameAndUniquifyCallback( +// DOWNLOAD_INTERRUPT_REASON_NONE, new_path)); +ACTION_P2(ScheduleRenameAndUniquifyCallback, interrupt_reason, new_path) { BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(arg1, interrupt_reason, new_path)); } +// Schedules a task to invoke the RenameCompletionCallback with |new_path| on +// the UI thread. Should only be used as the action for +// MockDownloadFile::RenameAndAnnotate as follows: +// EXPECT_CALL(download_file, RenameAndAnnotate(_,_,_,_,_)) +// .WillOnce(ScheduleRenameAndAnnotateCallback( +// DOWNLOAD_INTERRUPT_REASON_NONE, new_path)); +ACTION_P2(ScheduleRenameAndAnnotateCallback, interrupt_reason, new_path) { + BrowserThread::PostTask(BrowserThread::UI, + FROM_HERE, + base::Bind(arg4, interrupt_reason, new_path)); +} + +// Schedules a task to invoke a callback that's bound to the specified +// parameter. +// E.g.: +// +// EXPECT_CALL(foo, Bar(1, _)) +// .WithArg<1>(ScheduleCallbackWithParam(0)); +// +// .. will invoke the second argument to Bar with 0 as the parameter. +ACTION_P(ScheduleCallbackWithParam, param) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(arg0, param)); +} + +// Schedules a task to invoke a closure. +// E.g.: +// +// EXPECT_CALL(foo, Bar(1, _)) +// .WillOnce(ScheduleClosure(some_closure)); +ACTION_P(ScheduleClosure, closure) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, closure); +} + +const char kTestData1[] = {'M', 'a', 'r', 'y', ' ', 'h', 'a', 'd', + ' ', 'a', ' ', 'l', 'i', 't', 't', 'l', + 'e', ' ', 'l', 'a', 'm', 'b', '.'}; + +// SHA256 hash of TestData1 +const uint8_t kHashOfTestData1[] = { + 0xd2, 0xfc, 0x16, 0xa1, 0xf5, 0x1a, 0x65, 0x3a, 0xa0, 0x19, 0x64, + 0xef, 0x9c, 0x92, 0x33, 0x36, 0xe1, 0x06, 0x53, 0xfe, 0xc1, 0x95, + 0xf4, 0x93, 0x45, 0x8b, 0x3b, 0x21, 0x89, 0x0e, 0x1b, 0x97}; + } // namespace class DownloadItemTest : public testing::Test { public: DownloadItemTest() - : ui_thread_(BrowserThread::UI, &loop_), - file_thread_(BrowserThread::FILE, &loop_), - delegate_() { - } - - ~DownloadItemTest() { - } - - virtual void SetUp() { + : delegate_(), next_download_id_(DownloadItem::kInvalidId + 1) { base::FeatureList::ClearInstanceForTesting(); scoped_ptr<base::FeatureList> feature_list(new base::FeatureList); feature_list->InitializeFromCommandLine(features::kDownloadResumption.name, std::string()); base::FeatureList::SetInstance(std::move(feature_list)); - } - virtual void TearDown() { - ui_thread_.DeprecatedGetThreadObject()->message_loop()->RunUntilIdle(); - STLDeleteElements(&allocated_downloads_); + create_info_.reset(new DownloadCreateInfo()); + create_info_->save_info = + scoped_ptr<DownloadSaveInfo>(new DownloadSaveInfo()); + create_info_->save_info->prompt_for_save_location = false; + create_info_->url_chain.push_back(GURL("http://example.com/download")); + create_info_->etag = "SomethingToSatisfyResumption"; } - // This class keeps ownership of the created download item; it will - // be torn down at the end of the test unless DestroyDownloadItem is - // called. - DownloadItemImpl* CreateDownloadItem() { - scoped_ptr<DownloadCreateInfo> info; - - info.reset(new DownloadCreateInfo()); - info->save_info = scoped_ptr<DownloadSaveInfo>(new DownloadSaveInfo()); - info->save_info->prompt_for_save_location = false; - info->url_chain.push_back(GURL()); - info->etag = "SomethingToSatisfyResumption"; - - return CreateDownloadItemWithCreateInfo(std::move(info)); + ~DownloadItemTest() { + RunAllPendingInMessageLoops(); + STLDeleteElements(&allocated_downloads_); } DownloadItemImpl* CreateDownloadItemWithCreateInfo( @@ -231,26 +273,47 @@ class DownloadItemTest : public testing::Test { return download; } - // Add DownloadFile to DownloadItem - MockDownloadFile* AddDownloadFileToDownloadItem( + // This class keeps ownership of the created download item; it will + // be torn down at the end of the test unless DestroyDownloadItem is + // called. + DownloadItemImpl* CreateDownloadItem() { + create_info_->download_id = ++next_download_id_; + DownloadItemImpl* download = + new DownloadItemImpl(&delegate_, create_info_->download_id, + *create_info_, net::BoundNetLog()); + allocated_downloads_.insert(download); + return download; + } + + // Add DownloadFile to DownloadItem. Set |callback| to nullptr if a download + // target determination is not expected. + MockDownloadFile* CallDownloadItemStart( DownloadItemImpl* item, - DownloadItemImplDelegate::DownloadTargetCallback *callback) { - MockDownloadFile* mock_download_file(new StrictMock<MockDownloadFile>); - scoped_ptr<DownloadFile> download_file(mock_download_file); - EXPECT_CALL(*mock_download_file, Initialize(_)); + DownloadItemImplDelegate::DownloadTargetCallback* callback) { + MockDownloadFile* mock_download_file = nullptr; + scoped_ptr<DownloadFile> download_file; if (callback) { - // Save the callback. EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _)) .WillOnce(SaveArg<1>(callback)); } else { - // Drop it on the floor. - EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _)); + EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _)).Times(0); + } + + // Only create a DownloadFile if the request was successful. + if (create_info_->result == DOWNLOAD_INTERRUPT_REASON_NONE) { + mock_download_file = new StrictMock<MockDownloadFile>; + download_file.reset(mock_download_file); + EXPECT_CALL(*mock_download_file, Initialize(_)) + .WillOnce(ScheduleCallbackWithParam(DOWNLOAD_INTERRUPT_REASON_NONE)); + EXPECT_CALL(*mock_download_file, FullPath()) + .WillRepeatedly(ReturnRefOfCopy(base::FilePath())); } - scoped_ptr<DownloadRequestHandleInterface> request_handle( + scoped_ptr<MockRequestHandle> request_handle( new NiceMock<MockRequestHandle>); - item->Start(std::move(download_file), std::move(request_handle)); - loop_.RunUntilIdle(); + item->Start(std::move(download_file), std::move(request_handle), + *create_info_); + RunAllPendingInMessageLoops(); // So that we don't have a function writing to a stack variable // lying around if the above failed. @@ -266,27 +329,42 @@ class DownloadItemTest : public testing::Test { } // Perform the intermediate rename for |item|. The target path for the - // download will be set to kDummyPath. Returns the MockDownloadFile* that was - // added to the DownloadItem. + // download will be set to kDummyTargetPath. Returns the MockDownloadFile* + // that was added to the DownloadItem. MockDownloadFile* DoIntermediateRename(DownloadItemImpl* item, DownloadDangerType danger_type) { EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); EXPECT_TRUE(item->GetTargetFilePath().empty()); DownloadItemImplDelegate::DownloadTargetCallback callback; - MockDownloadFile* download_file = - AddDownloadFileToDownloadItem(item, &callback); - base::FilePath target_path(kDummyPath); - base::FilePath intermediate_path( - target_path.InsertBeforeExtensionASCII("x")); + MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); + base::FilePath target_path(kDummyTargetPath); + base::FilePath intermediate_path(kDummyIntermediatePath); EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - intermediate_path)); + .WillOnce(ScheduleRenameAndUniquifyCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, intermediate_path)); callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE, danger_type, intermediate_path); RunAllPendingInMessageLoops(); return download_file; } + void DoDestinationComplete(DownloadItemImpl* item, + MockDownloadFile* download_file) { + EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(_, _)) + .WillOnce(Return(true)); + base::FilePath final_path(kDummyTargetPath); + EXPECT_CALL(*download_file, RenameAndAnnotate(_, _, _, _, _)) + .WillOnce(ScheduleRenameAndAnnotateCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, final_path)); + EXPECT_CALL(*download_file, FullPath()) + .WillRepeatedly(ReturnRefOfCopy(base::FilePath(kDummyTargetPath))); + EXPECT_CALL(*download_file, Detach()); + + item->DestinationObserverAsWeakPtr()->DestinationCompleted( + 0, scoped_ptr<crypto::SecureHash>()); + RunAllPendingInMessageLoops(); + } + // Cleanup a download item (specifically get rid of the DownloadFile on it). // The item must be in the expected state. void CleanupItem(DownloadItemImpl* item, @@ -298,7 +376,7 @@ class DownloadItemTest : public testing::Test { if (download_file) EXPECT_CALL(*download_file, Cancel()); item->Cancel(true); - loop_.RunUntilIdle(); + RunAllPendingInMessageLoops(); } } @@ -308,9 +386,7 @@ class DownloadItemTest : public testing::Test { delete item; } - void RunAllPendingInMessageLoops() { - loop_.RunUntilIdle(); - } + void RunAllPendingInMessageLoops() { base::RunLoop().RunUntilIdle(); } MockDelegate* mock_delegate() { return &delegate_; @@ -321,13 +397,17 @@ class DownloadItemTest : public testing::Test { *return_path = path; } + DownloadCreateInfo* create_info() { return create_info_.get(); } + + BrowserContext* browser_context() { return &browser_context_; } + private: - int next_download_id_ = DownloadItem::kInvalidId + 1; - base::MessageLoopForUI loop_; - TestBrowserThread ui_thread_; // UI thread - TestBrowserThread file_thread_; // FILE thread StrictMock<MockDelegate> delegate_; std::set<DownloadItem*> allocated_downloads_; + scoped_ptr<DownloadCreateInfo> create_info_; + uint32_t next_download_id_ = DownloadItem::kInvalidId + 1; + TestBrowserContext browser_context_; + TestBrowserThreadBundle thread_bundle_; }; // Tests to ensure calls that change a DownloadItem generate an update to @@ -340,28 +420,33 @@ class DownloadItemTest : public testing::Test { TEST_F(DownloadItemTest, NotificationAfterUpdate) { DownloadItemImpl* item = CreateDownloadItem(); + MockDownloadFile* file = + DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); TestDownloadItemObserver observer(item); - item->DestinationUpdate(kDownloadChunkSize, kDownloadSpeed, std::string()); + item->DestinationUpdate(kDownloadChunkSize, kDownloadSpeed); ASSERT_TRUE(observer.CheckAndResetDownloadUpdated()); EXPECT_EQ(kDownloadSpeed, item->CurrentSpeed()); + CleanupItem(item, file, DownloadItem::IN_PROGRESS); } TEST_F(DownloadItemTest, NotificationAfterCancel) { DownloadItemImpl* user_cancel = CreateDownloadItem(); + DownloadItemImplDelegate::DownloadTargetCallback target_callback; MockDownloadFile* download_file = - AddDownloadFileToDownloadItem(user_cancel, NULL); + CallDownloadItemStart(user_cancel, &target_callback); EXPECT_CALL(*download_file, Cancel()); - TestDownloadItemObserver observer1(user_cancel); + TestDownloadItemObserver observer1(user_cancel); user_cancel->Cancel(true); ASSERT_TRUE(observer1.CheckAndResetDownloadUpdated()); DownloadItemImpl* system_cancel = CreateDownloadItem(); - download_file = AddDownloadFileToDownloadItem(system_cancel, NULL); + download_file = CallDownloadItemStart(system_cancel, &target_callback); EXPECT_CALL(*download_file, Cancel()); - TestDownloadItemObserver observer2(system_cancel); + TestDownloadItemObserver observer2(system_cancel); system_cancel->Cancel(false); ASSERT_TRUE(observer2.CheckAndResetDownloadUpdated()); } @@ -369,11 +454,10 @@ TEST_F(DownloadItemTest, NotificationAfterCancel) { TEST_F(DownloadItemTest, NotificationAfterComplete) { DownloadItemImpl* item = CreateDownloadItem(); TestDownloadItemObserver observer(item); - - item->OnAllDataSaved(DownloadItem::kEmptyFileHash); + MockDownloadFile* download_file = + DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); ASSERT_TRUE(observer.CheckAndResetDownloadUpdated()); - - item->MarkAsComplete(); + DoDestinationComplete(item, download_file); ASSERT_TRUE(observer.CheckAndResetDownloadUpdated()); } @@ -396,7 +480,9 @@ TEST_F(DownloadItemTest, NotificationAfterInterrupted) { .Times(0); item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, + 0, + scoped_ptr<crypto::SecureHash>()); ASSERT_TRUE(observer.CheckAndResetDownloadUpdated()); } @@ -408,8 +494,8 @@ TEST_F(DownloadItemTest, NotificationAfterDestroyed) { ASSERT_TRUE(observer.download_destroyed()); } +// Test that a download is resumed automatcially after a continuable interrupt. TEST_F(DownloadItemTest, ContinueAfterInterrupted) { - TestBrowserContext test_browser_context; DownloadItemImpl* item = CreateDownloadItem(); TestDownloadItemObserver observer(item); MockDownloadFile* download_file = @@ -417,13 +503,15 @@ TEST_F(DownloadItemTest, ContinueAfterInterrupted) { // Interrupt the download, using a continuable interrupt. EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(base::FilePath())); + .WillOnce(ReturnRefOfCopy(base::FilePath())); EXPECT_CALL(*download_file, Detach()); EXPECT_CALL(*mock_delegate(), GetBrowserContext()) - .WillRepeatedly(Return(&test_browser_context)); + .WillRepeatedly(Return(browser_context())); EXPECT_CALL(*mock_delegate(), MockResumeInterruptedDownload(_, _)).Times(1); item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR); + DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, + 0, + scoped_ptr<crypto::SecureHash>()); ASSERT_TRUE(observer.CheckAndResetDownloadUpdated()); // Since the download is resumed automatically, the interrupt count doesn't // increase. @@ -441,7 +529,8 @@ TEST_F(DownloadItemTest, ContinueAfterInterrupted) { CleanupItem(item, nullptr, DownloadItem::IN_PROGRESS); } -// Same as above, but with a non-continuable interrupt. +// Test that automatic resumption doesn't happen after a non-continuable +// interrupt. TEST_F(DownloadItemTest, RestartAfterInterrupted) { DownloadItemImpl* item = CreateDownloadItem(); TestDownloadItemObserver observer(item); @@ -451,7 +540,9 @@ TEST_F(DownloadItemTest, RestartAfterInterrupted) { // Interrupt the download, using a restartable interrupt. EXPECT_CALL(*download_file, Cancel()); item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, + 0, + scoped_ptr<crypto::SecureHash>()); ASSERT_TRUE(observer.CheckAndResetDownloadUpdated()); // Should not try to auto-resume. ASSERT_EQ(1, observer.interrupt_count()); @@ -471,13 +562,15 @@ TEST_F(DownloadItemTest, UnresumableInterrupt) { // Fail final rename with unresumable reason. EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _)) .WillOnce(Return(true)); - EXPECT_CALL(*download_file, RenameAndAnnotate(base::FilePath(kDummyPath), _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED, - base::FilePath(kDummyPath))); + EXPECT_CALL(*download_file, + RenameAndAnnotate(base::FilePath(kDummyTargetPath), _, _, _, _)) + .WillOnce(ScheduleRenameAndAnnotateCallback( + DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED, base::FilePath())); EXPECT_CALL(*download_file, Cancel()); // Complete download to trigger final rename. - item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string()); + item->DestinationObserverAsWeakPtr()->DestinationCompleted( + 0, scoped_ptr<crypto::SecureHash>()); RunAllPendingInMessageLoops(); ASSERT_TRUE(observer.CheckAndResetDownloadUpdated()); @@ -489,25 +582,24 @@ TEST_F(DownloadItemTest, UnresumableInterrupt) { } TEST_F(DownloadItemTest, LimitRestartsAfterInterrupted) { - TestBrowserContext test_browser_context; DownloadItemImpl* item = CreateDownloadItem(); base::WeakPtr<DownloadDestinationObserver> as_observer( item->DestinationObserverAsWeakPtr()); TestDownloadItemObserver observer(item); - MockDownloadFile* mock_download_file(NULL); + MockDownloadFile* mock_download_file(nullptr); scoped_ptr<DownloadFile> download_file; - MockRequestHandle* mock_request_handle(NULL); + MockRequestHandle* mock_request_handle(nullptr); scoped_ptr<DownloadRequestHandleInterface> request_handle; DownloadItemImplDelegate::DownloadTargetCallback callback; EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _)) .WillRepeatedly(SaveArg<1>(&callback)); EXPECT_CALL(*mock_delegate(), GetBrowserContext()) - .WillRepeatedly(Return(&test_browser_context)); + .WillRepeatedly(Return(browser_context())); EXPECT_CALL(*mock_delegate(), MockResumeInterruptedDownload(_, _)) .Times(DownloadItemImpl::kMaxAutoResumeAttempts); for (int i = 0; i < (DownloadItemImpl::kMaxAutoResumeAttempts + 1); ++i) { - DVLOG(20) << "Loop iteration " << i; + SCOPED_TRACE(::testing::Message() << "Iteration " << i); mock_download_file = new NiceMock<MockDownloadFile>; download_file.reset(mock_download_file); @@ -515,65 +607,128 @@ TEST_F(DownloadItemTest, LimitRestartsAfterInterrupted) { request_handle.reset(mock_request_handle); ON_CALL(*mock_download_file, FullPath()) - .WillByDefault(Return(base::FilePath())); + .WillByDefault(ReturnRefOfCopy(base::FilePath())); - // Copied key parts of DoIntermediateRename & AddDownloadFileToDownloadItem + // Copied key parts of DoIntermediateRename & CallDownloadItemStart // to allow for holding onto the request handle. - item->Start(std::move(download_file), std::move(request_handle)); + item->Start(std::move(download_file), std::move(request_handle), + *create_info()); RunAllPendingInMessageLoops(); + + base::FilePath target_path(kDummyTargetPath); + base::FilePath intermediate_path(kDummyIntermediatePath); if (i == 0) { - // Target determination is only done the first time through. - base::FilePath target_path(kDummyPath); - base::FilePath intermediate_path( - target_path.InsertBeforeExtensionASCII("x")); + // RenameAndUniquify is only called the first time. In all the subsequent + // iterations, the intermediate file already has the correct name, hence + // no rename is necessary. EXPECT_CALL(*mock_download_file, RenameAndUniquify(intermediate_path, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - intermediate_path)); - callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE, - DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path); - RunAllPendingInMessageLoops(); + .WillOnce(ScheduleRenameAndUniquifyCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, intermediate_path)); } + callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path); + RunAllPendingInMessageLoops(); // Use a continuable interrupt. item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR); + DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, + 0, + scoped_ptr<crypto::SecureHash>()); + RunAllPendingInMessageLoops(); ::testing::Mock::VerifyAndClearExpectations(mock_download_file); } + EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); EXPECT_EQ(1, observer.interrupt_count()); CleanupItem(item, nullptr, DownloadItem::INTERRUPTED); } +// If the download attempts to resume and the resumption request fails, the +// subsequent Start() call shouldn't update the origin state (URL redirect +// chains, Content-Disposition, download URL, etc..) +TEST_F(DownloadItemTest, FailedResumptionDoesntUpdateOriginState) { + const char kContentDisposition[] = "attachment; filename=foo"; + const char kFirstETag[] = "ABC"; + const char kFirstLastModified[] = "Yesterday"; + const char kFirstURL[] = "http://www.example.com/download"; + create_info()->content_disposition = kContentDisposition; + create_info()->etag = kFirstETag; + create_info()->last_modified = kFirstLastModified; + create_info()->url_chain.push_back(GURL(kFirstURL)); + + DownloadItemImpl* item = CreateDownloadItem(); + MockDownloadFile* download_file = + DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + EXPECT_EQ(kContentDisposition, item->GetContentDisposition()); + EXPECT_EQ(kFirstETag, item->GetETag()); + EXPECT_EQ(kFirstLastModified, item->GetLastModifiedTime()); + EXPECT_EQ(kFirstURL, item->GetURL().spec()); + + EXPECT_CALL(*mock_delegate(), MockResumeInterruptedDownload(_, _)); + EXPECT_CALL(*mock_delegate(), GetBrowserContext()) + .WillRepeatedly(Return(browser_context())); + EXPECT_CALL(*download_file, Detach()); + item->DestinationObserverAsWeakPtr()->DestinationError( + DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, + 0, + scoped_ptr<crypto::SecureHash>()); + RunAllPendingInMessageLoops(); + EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + + // Now change the create info. The changes should not cause the DownloadItem + // to be updated. + const char kSecondContentDisposition[] = "attachment; filename=bar"; + const char kSecondETag[] = "123"; + const char kSecondLastModified[] = "Today"; + const char kSecondURL[] = "http://example.com/another-download"; + create_info()->content_disposition = kSecondContentDisposition; + create_info()->etag = kSecondETag; + create_info()->last_modified = kSecondLastModified; + create_info()->url_chain.clear(); + create_info()->url_chain.push_back(GURL(kSecondURL)); + create_info()->result = DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED; + + // The following ends up calling DownloadItem::Start(), but shouldn't result + // in an origin update. + download_file = CallDownloadItemStart(item, nullptr); + + EXPECT_EQ(kContentDisposition, item->GetContentDisposition()); + EXPECT_EQ(kFirstETag, item->GetETag()); + EXPECT_EQ(kFirstLastModified, item->GetLastModifiedTime()); + EXPECT_EQ(kFirstURL, item->GetURL().spec()); + EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, item->GetLastReason()); +} + // Test that resumption uses the final URL in a URL chain when resuming. TEST_F(DownloadItemTest, ResumeUsingFinalURL) { - TestBrowserContext test_browser_context; - scoped_ptr<DownloadCreateInfo> create_info(new DownloadCreateInfo); - create_info->save_info = scoped_ptr<DownloadSaveInfo>(new DownloadSaveInfo()); - create_info->save_info->prompt_for_save_location = false; - create_info->etag = "SomethingToSatisfyResumption"; - create_info->url_chain.push_back(GURL("http://example.com/a")); - create_info->url_chain.push_back(GURL("http://example.com/b")); - create_info->url_chain.push_back(GURL("http://example.com/c")); - - DownloadItemImpl* item = - CreateDownloadItemWithCreateInfo(std::move(create_info)); + create_info()->save_info->prompt_for_save_location = false; + create_info()->url_chain.clear(); + create_info()->url_chain.push_back(GURL("http://example.com/a")); + create_info()->url_chain.push_back(GURL("http://example.com/b")); + create_info()->url_chain.push_back(GURL("http://example.com/c")); + + DownloadItemImpl* item = CreateDownloadItem(); TestDownloadItemObserver observer(item); MockDownloadFile* download_file = DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); // Interrupt the download, using a continuable interrupt. - EXPECT_CALL(*download_file, FullPath()).WillOnce(Return(base::FilePath())); + EXPECT_CALL(*download_file, FullPath()) + .WillOnce(ReturnRefOfCopy(base::FilePath())); EXPECT_CALL(*download_file, Detach()); EXPECT_CALL(*mock_delegate(), GetBrowserContext()) - .WillRepeatedly(Return(&test_browser_context)); + .WillRepeatedly(Return(browser_context())); EXPECT_CALL(*mock_delegate(), MockResumeInterruptedDownload( Property(&DownloadUrlParameters::url, GURL("http://example.com/c")), _)) .Times(1); item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR); + DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, + 0, + scoped_ptr<crypto::SecureHash>()); // Test expectations verify that ResumeInterruptedDownload() is called (by way // of MockResumeInterruptedDownload) after the download is interrupted. But @@ -588,7 +743,9 @@ TEST_F(DownloadItemTest, ResumeUsingFinalURL) { TEST_F(DownloadItemTest, NotificationAfterRemove) { DownloadItemImpl* item = CreateDownloadItem(); - MockDownloadFile* download_file = AddDownloadFileToDownloadItem(item, NULL); + DownloadItemImplDelegate::DownloadTargetCallback target_callback; + MockDownloadFile* download_file = + CallDownloadItemStart(item, &target_callback); EXPECT_CALL(*download_file, Cancel()); EXPECT_CALL(*mock_delegate(), DownloadRemoved(_)); TestDownloadItemObserver observer(item); @@ -601,37 +758,52 @@ TEST_F(DownloadItemTest, NotificationAfterRemove) { TEST_F(DownloadItemTest, NotificationAfterOnContentCheckCompleted) { // Setting to NOT_DANGEROUS does not trigger a notification. DownloadItemImpl* safe_item = CreateDownloadItem(); + MockDownloadFile* download_file = + DoIntermediateRename(safe_item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); TestDownloadItemObserver safe_observer(safe_item); - safe_item->OnAllDataSaved(std::string()); + safe_item->OnAllDataSaved(0, scoped_ptr<crypto::SecureHash>()); EXPECT_TRUE(safe_observer.CheckAndResetDownloadUpdated()); safe_item->OnContentCheckCompleted(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); EXPECT_TRUE(safe_observer.CheckAndResetDownloadUpdated()); + CleanupItem(safe_item, download_file, DownloadItem::IN_PROGRESS); // Setting to unsafe url or unsafe file should trigger a notification. DownloadItemImpl* unsafeurl_item = CreateDownloadItem(); + download_file = + DoIntermediateRename(unsafeurl_item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); TestDownloadItemObserver unsafeurl_observer(unsafeurl_item); - unsafeurl_item->OnAllDataSaved(std::string()); + unsafeurl_item->OnAllDataSaved(0, scoped_ptr<crypto::SecureHash>()); EXPECT_TRUE(unsafeurl_observer.CheckAndResetDownloadUpdated()); unsafeurl_item->OnContentCheckCompleted(DOWNLOAD_DANGER_TYPE_DANGEROUS_URL); EXPECT_TRUE(unsafeurl_observer.CheckAndResetDownloadUpdated()); + EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(_, _)) + .WillOnce(Return(true)); + EXPECT_CALL(*download_file, RenameAndAnnotate(_, _, _, _, _)); unsafeurl_item->ValidateDangerousDownload(); EXPECT_TRUE(unsafeurl_observer.CheckAndResetDownloadUpdated()); + CleanupItem(unsafeurl_item, download_file, DownloadItem::IN_PROGRESS); DownloadItemImpl* unsafefile_item = CreateDownloadItem(); + download_file = + DoIntermediateRename(unsafefile_item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); TestDownloadItemObserver unsafefile_observer(unsafefile_item); - unsafefile_item->OnAllDataSaved(std::string()); + unsafefile_item->OnAllDataSaved(0, scoped_ptr<crypto::SecureHash>()); EXPECT_TRUE(unsafefile_observer.CheckAndResetDownloadUpdated()); unsafefile_item->OnContentCheckCompleted(DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE); EXPECT_TRUE(unsafefile_observer.CheckAndResetDownloadUpdated()); + EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(_, _)) + .WillOnce(Return(true)); + EXPECT_CALL(*download_file, RenameAndAnnotate(_, _, _, _, _)); unsafefile_item->ValidateDangerousDownload(); EXPECT_TRUE(unsafefile_observer.CheckAndResetDownloadUpdated()); + CleanupItem(unsafefile_item, download_file, DownloadItem::IN_PROGRESS); } // DownloadItemImpl::OnDownloadTargetDetermined will schedule a task to run @@ -642,16 +814,15 @@ TEST_F(DownloadItemTest, NotificationAfterOnContentCheckCompleted) { TEST_F(DownloadItemTest, NotificationAfterOnDownloadTargetDetermined) { DownloadItemImpl* item = CreateDownloadItem(); DownloadItemImplDelegate::DownloadTargetCallback callback; - MockDownloadFile* download_file = - AddDownloadFileToDownloadItem(item, &callback); + MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); TestDownloadItemObserver observer(item); - base::FilePath target_path(kDummyPath); + base::FilePath target_path(kDummyTargetPath); base::FilePath intermediate_path(target_path.InsertBeforeExtensionASCII("x")); base::FilePath new_intermediate_path( target_path.InsertBeforeExtensionASCII("y")); EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - new_intermediate_path)); + .WillOnce(ScheduleRenameAndUniquifyCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, new_intermediate_path)); // Currently, a notification would be generated if the danger type is anything // other than NOT_DANGEROUS. @@ -675,7 +846,8 @@ TEST_F(DownloadItemTest, NotificationAfterTogglePause) { EXPECT_CALL(*mock_download_file, Initialize(_)); EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(_, _)); - item->Start(std::move(download_file), std::move(request_handle)); + item->Start(std::move(download_file), std::move(request_handle), + *create_info()); item->Pause(); ASSERT_TRUE(observer.CheckAndResetDownloadUpdated()); @@ -693,15 +865,15 @@ TEST_F(DownloadItemTest, NotificationAfterTogglePause) { TEST_F(DownloadItemTest, DisplayName) { DownloadItemImpl* item = CreateDownloadItem(); DownloadItemImplDelegate::DownloadTargetCallback callback; - MockDownloadFile* download_file = - AddDownloadFileToDownloadItem(item, &callback); - base::FilePath target_path(base::FilePath(kDummyPath).AppendASCII("foo.bar")); + MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); + base::FilePath target_path( + base::FilePath(kDummyTargetPath).AppendASCII("foo.bar")); base::FilePath intermediate_path(target_path.InsertBeforeExtensionASCII("x")); EXPECT_EQ(FILE_PATH_LITERAL(""), item->GetFileNameToReportUser().value()); EXPECT_CALL(*download_file, RenameAndUniquify(_, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - intermediate_path)); + .WillOnce(ScheduleRenameAndUniquifyCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, intermediate_path)); callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path); RunAllPendingInMessageLoops(); @@ -722,25 +894,90 @@ TEST_F(DownloadItemTest, Start) { scoped_ptr<DownloadRequestHandleInterface> request_handle( new NiceMock<MockRequestHandle>); EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _)); - item->Start(std::move(download_file), std::move(request_handle)); + item->Start(std::move(download_file), std::move(request_handle), + *create_info()); RunAllPendingInMessageLoops(); CleanupItem(item, mock_download_file, DownloadItem::IN_PROGRESS); } +// Download file and the request should be cancelled as a result of download +// file initialization failing. +TEST_F(DownloadItemTest, InitDownloadFileFails) { + DownloadItemImpl* item = CreateDownloadItem(); + scoped_ptr<MockDownloadFile> file(new MockDownloadFile()); + scoped_ptr<MockRequestHandle> request_handle(new MockRequestHandle()); + EXPECT_CALL(*file, Cancel()); + EXPECT_CALL(*request_handle, CancelRequest()); + EXPECT_CALL(*file, Initialize(_)) + .WillOnce(ScheduleCallbackWithParam( + DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED)); + + base::RunLoop start_download_loop; + DownloadItemImplDelegate::DownloadTargetCallback download_target_callback; + EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _)) + .WillOnce(DoAll(SaveArg<1>(&download_target_callback), + ScheduleClosure(start_download_loop.QuitClosure()))); + + item->Start(std::move(file), std::move(request_handle), *create_info()); + start_download_loop.Run(); + + download_target_callback.Run(base::FilePath(kDummyTargetPath), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + base::FilePath(kDummyIntermediatePath)); + RunAllPendingInMessageLoops(); + + EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED, + item->GetLastReason()); +} + +// Handling of downloads initiated via a failed request. In this case, Start() +// will get called with a DownloadCreateInfo with a non-zero interrupt_reason. +TEST_F(DownloadItemTest, StartFailedDownload) { + create_info()->result = DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED; + DownloadItemImpl* item = CreateDownloadItem(); + + // DownloadFile and DownloadRequestHandleInterface objects aren't created for + // failed downloads. + scoped_ptr<DownloadFile> null_download_file; + scoped_ptr<DownloadRequestHandleInterface> null_request_handle; + DownloadItemImplDelegate::DownloadTargetCallback download_target_callback; + EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _)) + .WillOnce(SaveArg<1>(&download_target_callback)); + item->Start(std::move(null_download_file), std::move(null_request_handle), + *create_info()); + EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + RunAllPendingInMessageLoops(); + + // The DownloadItemImpl should attempt to determine a target path even if the + // download was interrupted. + ASSERT_FALSE(download_target_callback.is_null()); + ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + base::FilePath target_path(FILE_PATH_LITERAL("foo")); + download_target_callback.Run(target_path, + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, target_path); + RunAllPendingInMessageLoops(); + + EXPECT_EQ(target_path, item->GetTargetFilePath()); + CleanupItem(item, NULL, DownloadItem::INTERRUPTED); +} + // Test that the delegate is invoked after the download file is renamed. TEST_F(DownloadItemTest, CallbackAfterRename) { DownloadItemImpl* item = CreateDownloadItem(); DownloadItemImplDelegate::DownloadTargetCallback callback; - MockDownloadFile* download_file = - AddDownloadFileToDownloadItem(item, &callback); - base::FilePath final_path(base::FilePath(kDummyPath).AppendASCII("foo.bar")); + MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); + base::FilePath final_path( + base::FilePath(kDummyTargetPath).AppendASCII("foo.bar")); base::FilePath intermediate_path(final_path.InsertBeforeExtensionASCII("x")); base::FilePath new_intermediate_path( final_path.InsertBeforeExtensionASCII("y")); EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - new_intermediate_path)); + .WillOnce(ScheduleRenameAndUniquifyCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, new_intermediate_path)); callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path); @@ -751,13 +988,14 @@ TEST_F(DownloadItemTest, CallbackAfterRename) { EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _)) .WillOnce(Return(true)); - EXPECT_CALL(*download_file, RenameAndAnnotate(final_path, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - final_path)); + EXPECT_CALL(*download_file, RenameAndAnnotate(final_path, _, _, _, _)) + .WillOnce(ScheduleRenameAndAnnotateCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, final_path)); EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(base::FilePath())); + .WillOnce(ReturnRefOfCopy(base::FilePath())); EXPECT_CALL(*download_file, Detach()); - item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string()); + item->DestinationObserverAsWeakPtr()->DestinationCompleted( + 0, scoped_ptr<crypto::SecureHash>()); RunAllPendingInMessageLoops(); ::testing::Mock::VerifyAndClearExpectations(download_file); mock_delegate()->VerifyAndClearExpectations(); @@ -768,15 +1006,15 @@ TEST_F(DownloadItemTest, CallbackAfterRename) { TEST_F(DownloadItemTest, CallbackAfterInterruptedRename) { DownloadItemImpl* item = CreateDownloadItem(); DownloadItemImplDelegate::DownloadTargetCallback callback; - MockDownloadFile* download_file = - AddDownloadFileToDownloadItem(item, &callback); - base::FilePath final_path(base::FilePath(kDummyPath).AppendASCII("foo.bar")); + MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); + base::FilePath final_path( + base::FilePath(kDummyTargetPath).AppendASCII("foo.bar")); base::FilePath intermediate_path(final_path.InsertBeforeExtensionASCII("x")); base::FilePath new_intermediate_path( final_path.InsertBeforeExtensionASCII("y")); EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, - new_intermediate_path)); + .WillOnce(ScheduleRenameAndUniquifyCallback( + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, new_intermediate_path)); EXPECT_CALL(*download_file, Cancel()) .Times(1); @@ -798,7 +1036,8 @@ TEST_F(DownloadItemTest, Interrupted) { // Confirm interrupt sets state properly. EXPECT_CALL(*download_file, Cancel()); - item->DestinationObserverAsWeakPtr()->DestinationError(reason); + item->DestinationObserverAsWeakPtr()->DestinationError( + reason, 0, scoped_ptr<crypto::SecureHash>()); RunAllPendingInMessageLoops(); EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); EXPECT_EQ(reason, item->GetLastReason()); @@ -814,19 +1053,21 @@ TEST_F(DownloadItemTest, Interrupted) { TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Restart) { DownloadItemImpl* item = CreateDownloadItem(); DownloadItemImplDelegate::DownloadTargetCallback callback; - MockDownloadFile* download_file = - AddDownloadFileToDownloadItem(item, &callback); + MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, + 0, + scoped_ptr<crypto::SecureHash>()); ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - base::FilePath final_path(base::FilePath(kDummyPath).AppendASCII("foo.bar")); + base::FilePath final_path( + base::FilePath(kDummyTargetPath).AppendASCII("foo.bar")); base::FilePath intermediate_path(final_path.InsertBeforeExtensionASCII("x")); base::FilePath new_intermediate_path( final_path.InsertBeforeExtensionASCII("y")); EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - new_intermediate_path)); + .WillOnce(ScheduleRenameAndUniquifyCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, new_intermediate_path)); EXPECT_CALL(*download_file, Cancel()) .Times(1); @@ -847,21 +1088,23 @@ TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Restart) { TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Continue) { DownloadItemImpl* item = CreateDownloadItem(); DownloadItemImplDelegate::DownloadTargetCallback callback; - MockDownloadFile* download_file = - AddDownloadFileToDownloadItem(item, &callback); + MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED); + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, + 0, + scoped_ptr<crypto::SecureHash>()); ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - base::FilePath final_path(base::FilePath(kDummyPath).AppendASCII("foo.bar")); + base::FilePath final_path( + base::FilePath(kDummyTargetPath).AppendASCII("foo.bar")); base::FilePath intermediate_path(final_path.InsertBeforeExtensionASCII("x")); base::FilePath new_intermediate_path( final_path.InsertBeforeExtensionASCII("y")); EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - new_intermediate_path)); + .WillOnce(ScheduleRenameAndUniquifyCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, new_intermediate_path)); EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(base::FilePath(new_intermediate_path))); + .WillOnce(ReturnRefOfCopy(base::FilePath(new_intermediate_path))); EXPECT_CALL(*download_file, Detach()); callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE, @@ -876,23 +1119,25 @@ TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Continue) { } // As above. If the intermediate rename fails, then the interrupt reason should -// be set to the destination error and the intermediate path should be empty. +// be set to the file error and the intermediate path should be empty. TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Failed) { DownloadItemImpl* item = CreateDownloadItem(); DownloadItemImplDelegate::DownloadTargetCallback callback; - MockDownloadFile* download_file = - AddDownloadFileToDownloadItem(item, &callback); + MockDownloadFile* download_file = CallDownloadItemStart(item, &callback); item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED); + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, + 0, + scoped_ptr<crypto::SecureHash>()); ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); - base::FilePath final_path(base::FilePath(kDummyPath).AppendASCII("foo.bar")); + base::FilePath final_path( + base::FilePath(kDummyTargetPath).AppendASCII("foo.bar")); base::FilePath intermediate_path(final_path.InsertBeforeExtensionASCII("x")); base::FilePath new_intermediate_path( final_path.InsertBeforeExtensionASCII("y")); EXPECT_CALL(*download_file, RenameAndUniquify(intermediate_path, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, - new_intermediate_path)); + .WillOnce(ScheduleRenameAndUniquifyCallback( + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, new_intermediate_path)); EXPECT_CALL(*download_file, Cancel()) .Times(1); @@ -903,14 +1148,16 @@ TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Failed) { ::testing::Mock::VerifyAndClearExpectations(download_file); mock_delegate()->VerifyAndClearExpectations(); EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); - EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, item->GetLastReason()); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, item->GetLastReason()); EXPECT_TRUE(item->GetFullPath().empty()); EXPECT_EQ(final_path, item->GetTargetFilePath()); } TEST_F(DownloadItemTest, Canceled) { DownloadItemImpl* item = CreateDownloadItem(); - MockDownloadFile* download_file = AddDownloadFileToDownloadItem(item, NULL); + DownloadItemImplDelegate::DownloadTargetCallback target_callback; + MockDownloadFile* download_file = + CallDownloadItemStart(item, &target_callback); // Confirm cancel sets state properly. EXPECT_CALL(*download_file, Cancel()); @@ -928,34 +1175,62 @@ TEST_F(DownloadItemTest, FileRemoved) { TEST_F(DownloadItemTest, DestinationUpdate) { DownloadItemImpl* item = CreateDownloadItem(); + MockDownloadFile* file = + DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); base::WeakPtr<DownloadDestinationObserver> as_observer( item->DestinationObserverAsWeakPtr()); TestDownloadItemObserver observer(item); EXPECT_EQ(0l, item->CurrentSpeed()); - EXPECT_EQ("", item->GetHashState()); EXPECT_EQ(0l, item->GetReceivedBytes()); EXPECT_EQ(0l, item->GetTotalBytes()); EXPECT_FALSE(observer.CheckAndResetDownloadUpdated()); item->SetTotalBytes(100l); EXPECT_EQ(100l, item->GetTotalBytes()); - as_observer->DestinationUpdate(10, 20, "deadbeef"); + as_observer->DestinationUpdate(10, 20); EXPECT_EQ(20l, item->CurrentSpeed()); - EXPECT_EQ("deadbeef", item->GetHashState()); EXPECT_EQ(10l, item->GetReceivedBytes()); EXPECT_EQ(100l, item->GetTotalBytes()); EXPECT_TRUE(observer.CheckAndResetDownloadUpdated()); - as_observer->DestinationUpdate(200, 20, "livebeef"); + as_observer->DestinationUpdate(200, 20); EXPECT_EQ(20l, item->CurrentSpeed()); - EXPECT_EQ("livebeef", item->GetHashState()); EXPECT_EQ(200l, item->GetReceivedBytes()); EXPECT_EQ(0l, item->GetTotalBytes()); EXPECT_TRUE(observer.CheckAndResetDownloadUpdated()); + + CleanupItem(item, file, DownloadItem::IN_PROGRESS); } -TEST_F(DownloadItemTest, DestinationError) { +TEST_F(DownloadItemTest, DestinationError_NoRestartRequired) { + DownloadItemImpl* item = CreateDownloadItem(); + MockDownloadFile* download_file = + DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + base::WeakPtr<DownloadDestinationObserver> as_observer( + item->DestinationObserverAsWeakPtr()); + TestDownloadItemObserver observer(item); + + EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, item->GetLastReason()); + EXPECT_FALSE(observer.CheckAndResetDownloadUpdated()); + + scoped_ptr<crypto::SecureHash> hash( + crypto::SecureHash::Create(crypto::SecureHash::SHA256)); + hash->Update(kTestData1, sizeof(kTestData1)); + + EXPECT_CALL(*download_file, Detach()); + as_observer->DestinationError( + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, 0, std::move(hash)); + mock_delegate()->VerifyAndClearExpectations(); + EXPECT_TRUE(observer.CheckAndResetDownloadUpdated()); + EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, item->GetLastReason()); + EXPECT_EQ( + std::string(std::begin(kHashOfTestData1), std::end(kHashOfTestData1)), + item->GetHash()); +} +TEST_F(DownloadItemTest, DestinationError_RestartRequired) { DownloadItemImpl* item = CreateDownloadItem(); MockDownloadFile* download_file = DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); @@ -967,43 +1242,58 @@ TEST_F(DownloadItemTest, DestinationError) { EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, item->GetLastReason()); EXPECT_FALSE(observer.CheckAndResetDownloadUpdated()); + scoped_ptr<crypto::SecureHash> hash( + crypto::SecureHash::Create(crypto::SecureHash::SHA256)); + hash->Update(kTestData1, sizeof(kTestData1)); + EXPECT_CALL(*download_file, Cancel()); as_observer->DestinationError( - DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED); + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, 0, std::move(hash)); mock_delegate()->VerifyAndClearExpectations(); EXPECT_TRUE(observer.CheckAndResetDownloadUpdated()); EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState()); - EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED, - item->GetLastReason()); + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, item->GetLastReason()); + EXPECT_EQ(std::string(), item->GetHash()); } TEST_F(DownloadItemTest, DestinationCompleted) { DownloadItemImpl* item = CreateDownloadItem(); + MockDownloadFile* download_file = + DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); base::WeakPtr<DownloadDestinationObserver> as_observer( item->DestinationObserverAsWeakPtr()); TestDownloadItemObserver observer(item); EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); EXPECT_EQ("", item->GetHash()); - EXPECT_EQ("", item->GetHashState()); EXPECT_FALSE(item->AllDataSaved()); EXPECT_FALSE(observer.CheckAndResetDownloadUpdated()); - as_observer->DestinationUpdate(10, 20, "deadbeef"); + as_observer->DestinationUpdate(10, 20); EXPECT_TRUE(observer.CheckAndResetDownloadUpdated()); EXPECT_FALSE(observer.CheckAndResetDownloadUpdated()); // Confirm reset. EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); EXPECT_EQ("", item->GetHash()); - EXPECT_EQ("deadbeef", item->GetHashState()); EXPECT_FALSE(item->AllDataSaved()); - as_observer->DestinationCompleted("livebeef"); + scoped_ptr<crypto::SecureHash> hash( + crypto::SecureHash::Create(crypto::SecureHash::SHA256)); + hash->Update(kTestData1, sizeof(kTestData1)); + + EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(_, _)); + as_observer->DestinationCompleted(10, std::move(hash)); mock_delegate()->VerifyAndClearExpectations(); EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); EXPECT_TRUE(observer.CheckAndResetDownloadUpdated()); - EXPECT_EQ("livebeef", item->GetHash()); - EXPECT_EQ("", item->GetHashState()); + EXPECT_EQ( + std::string(std::begin(kHashOfTestData1), std::end(kHashOfTestData1)), + item->GetHash()); EXPECT_TRUE(item->AllDataSaved()); + + // Even though the DownloadItem receives a DestinationCompleted() event, + // target determination hasn't completed, hence the download item is stuck in + // TARGET_PENDING. + CleanupItem(item, download_file, DownloadItem::IN_PROGRESS); } TEST_F(DownloadItemTest, EnabledActionsForNormalDownload) { @@ -1018,15 +1308,16 @@ TEST_F(DownloadItemTest, EnabledActionsForNormalDownload) { EXPECT_TRUE(item->CanOpenDownload()); // Complete - EXPECT_CALL(*download_file, RenameAndAnnotate(_, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - base::FilePath(kDummyPath))); + EXPECT_CALL(*download_file, RenameAndAnnotate(_, _, _, _, _)) + .WillOnce(ScheduleRenameAndAnnotateCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, base::FilePath(kDummyTargetPath))); EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _)) .WillOnce(Return(true)); EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(base::FilePath())); + .WillOnce(ReturnRefOfCopy(base::FilePath())); EXPECT_CALL(*download_file, Detach()); - item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string()); + item->DestinationObserverAsWeakPtr()->DestinationCompleted( + 0, scoped_ptr<crypto::SecureHash>()); RunAllPendingInMessageLoops(); ASSERT_EQ(DownloadItem::COMPLETE, item->GetState()); @@ -1050,13 +1341,14 @@ TEST_F(DownloadItemTest, EnabledActionsForTemporaryDownload) { // Complete Temporary EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _)) .WillOnce(Return(true)); - EXPECT_CALL(*download_file, RenameAndAnnotate(_, _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - base::FilePath(kDummyPath))); + EXPECT_CALL(*download_file, RenameAndAnnotate(_, _, _, _, _)) + .WillOnce(ScheduleRenameAndAnnotateCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, base::FilePath(kDummyTargetPath))); EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(base::FilePath())); + .WillOnce(ReturnRefOfCopy(base::FilePath())); EXPECT_CALL(*download_file, Detach()); - item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string()); + item->DestinationObserverAsWeakPtr()->DestinationCompleted( + 0, scoped_ptr<crypto::SecureHash>()); RunAllPendingInMessageLoops(); ASSERT_EQ(DownloadItem::COMPLETE, item->GetState()); @@ -1071,13 +1363,15 @@ TEST_F(DownloadItemTest, EnabledActionsForInterruptedDownload) { EXPECT_CALL(*download_file, Cancel()); item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, + 0, + scoped_ptr<crypto::SecureHash>()); RunAllPendingInMessageLoops(); ASSERT_EQ(DownloadItem::INTERRUPTED, item->GetState()); ASSERT_FALSE(item->GetTargetFilePath().empty()); EXPECT_FALSE(item->CanShowInFolder()); - EXPECT_FALSE(item->CanOpenDownload()); + EXPECT_TRUE(item->CanOpenDownload()); } TEST_F(DownloadItemTest, EnabledActionsForCancelledDownload) { @@ -1107,18 +1401,20 @@ TEST_F(DownloadItemTest, CompleteDelegate_ReturnTrue) { // Drive the delegate interaction. EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(item, _)) .WillOnce(Return(true)); - item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string()); + item->DestinationObserverAsWeakPtr()->DestinationCompleted( + 0, scoped_ptr<crypto::SecureHash>()); EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); EXPECT_FALSE(item->IsDangerous()); // Make sure the download can complete. - EXPECT_CALL(*download_file, RenameAndAnnotate(base::FilePath(kDummyPath), _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - base::FilePath(kDummyPath))); + EXPECT_CALL(*download_file, + RenameAndAnnotate(base::FilePath(kDummyTargetPath), _, _, _, _)) + .WillOnce(ScheduleRenameAndAnnotateCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, base::FilePath(kDummyTargetPath))); EXPECT_CALL(*mock_delegate(), ShouldOpenDownload(item, _)) .WillOnce(Return(true)); EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(base::FilePath())); + .WillOnce(ReturnRefOfCopy(base::FilePath())); EXPECT_CALL(*download_file, Detach()); RunAllPendingInMessageLoops(); EXPECT_EQ(DownloadItem::COMPLETE, item->GetState()); @@ -1139,7 +1435,8 @@ TEST_F(DownloadItemTest, CompleteDelegate_BlockOnce) { .WillOnce(DoAll(SaveArg<1>(&delegate_callback), Return(false))) .WillOnce(Return(true)); - item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string()); + item->DestinationObserverAsWeakPtr()->DestinationCompleted( + 0, scoped_ptr<crypto::SecureHash>()); ASSERT_FALSE(delegate_callback.is_null()); copy_delegate_callback = delegate_callback; delegate_callback.Reset(); @@ -1150,13 +1447,14 @@ TEST_F(DownloadItemTest, CompleteDelegate_BlockOnce) { EXPECT_FALSE(item->IsDangerous()); // Make sure the download can complete. - EXPECT_CALL(*download_file, RenameAndAnnotate(base::FilePath(kDummyPath), _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - base::FilePath(kDummyPath))); + EXPECT_CALL(*download_file, + RenameAndAnnotate(base::FilePath(kDummyTargetPath), _, _, _, _)) + .WillOnce(ScheduleRenameAndAnnotateCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, base::FilePath(kDummyTargetPath))); EXPECT_CALL(*mock_delegate(), ShouldOpenDownload(item, _)) .WillOnce(Return(true)); EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(base::FilePath())); + .WillOnce(ReturnRefOfCopy(base::FilePath())); EXPECT_CALL(*download_file, Detach()); RunAllPendingInMessageLoops(); EXPECT_EQ(DownloadItem::COMPLETE, item->GetState()); @@ -1177,7 +1475,8 @@ TEST_F(DownloadItemTest, CompleteDelegate_SetDanger) { .WillOnce(DoAll(SaveArg<1>(&delegate_callback), Return(false))) .WillOnce(Return(true)); - item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string()); + item->DestinationObserverAsWeakPtr()->DestinationCompleted( + 0, scoped_ptr<crypto::SecureHash>()); ASSERT_FALSE(delegate_callback.is_null()); copy_delegate_callback = delegate_callback; delegate_callback.Reset(); @@ -1191,13 +1490,14 @@ TEST_F(DownloadItemTest, CompleteDelegate_SetDanger) { EXPECT_TRUE(item->IsDangerous()); // Make sure the download doesn't complete until we've validated it. - EXPECT_CALL(*download_file, RenameAndAnnotate(base::FilePath(kDummyPath), _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - base::FilePath(kDummyPath))); + EXPECT_CALL(*download_file, + RenameAndAnnotate(base::FilePath(kDummyTargetPath), _, _, _, _)) + .WillOnce(ScheduleRenameAndAnnotateCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, base::FilePath(kDummyTargetPath))); EXPECT_CALL(*mock_delegate(), ShouldOpenDownload(item, _)) .WillOnce(Return(true)); EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(base::FilePath())); + .WillOnce(ReturnRefOfCopy(base::FilePath())); EXPECT_CALL(*download_file, Detach()); RunAllPendingInMessageLoops(); EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState()); @@ -1226,7 +1526,8 @@ TEST_F(DownloadItemTest, CompleteDelegate_BlockTwice) { .WillOnce(DoAll(SaveArg<1>(&delegate_callback), Return(false))) .WillOnce(Return(true)); - item->DestinationObserverAsWeakPtr()->DestinationCompleted(std::string()); + item->DestinationObserverAsWeakPtr()->DestinationCompleted( + 0, scoped_ptr<crypto::SecureHash>()); ASSERT_FALSE(delegate_callback.is_null()); copy_delegate_callback = delegate_callback; delegate_callback.Reset(); @@ -1242,13 +1543,14 @@ TEST_F(DownloadItemTest, CompleteDelegate_BlockTwice) { EXPECT_FALSE(item->IsDangerous()); // Make sure the download can complete. - EXPECT_CALL(*download_file, RenameAndAnnotate(base::FilePath(kDummyPath), _)) - .WillOnce(ScheduleRenameCallback(DOWNLOAD_INTERRUPT_REASON_NONE, - base::FilePath(kDummyPath))); + EXPECT_CALL(*download_file, + RenameAndAnnotate(base::FilePath(kDummyTargetPath), _, _, _, _)) + .WillOnce(ScheduleRenameAndAnnotateCallback( + DOWNLOAD_INTERRUPT_REASON_NONE, base::FilePath(kDummyTargetPath))); EXPECT_CALL(*mock_delegate(), ShouldOpenDownload(item, _)) .WillOnce(Return(true)); EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(base::FilePath())); + .WillOnce(ReturnRefOfCopy(base::FilePath())); EXPECT_CALL(*download_file, Detach()); RunAllPendingInMessageLoops(); EXPECT_EQ(DownloadItem::COMPLETE, item->GetState()); @@ -1262,8 +1564,7 @@ TEST_F(DownloadItemTest, StealDangerousDownload) { base::FilePath full_path(FILE_PATH_LITERAL("foo.txt")); base::FilePath returned_path; - EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(full_path)); + EXPECT_CALL(*download_file, FullPath()).WillOnce(ReturnRefOfCopy(full_path)); EXPECT_CALL(*download_file, Detach()); EXPECT_CALL(*mock_delegate(), DownloadRemoved(_)); base::WeakPtrFactory<DownloadItemTest> weak_ptr_factory(this); @@ -1282,11 +1583,12 @@ TEST_F(DownloadItemTest, StealInterruptedDangerousDownload) { DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE); base::FilePath full_path = item->GetFullPath(); EXPECT_FALSE(full_path.empty()); - EXPECT_CALL(*download_file, FullPath()) - .WillOnce(Return(full_path)); + EXPECT_CALL(*download_file, FullPath()).WillOnce(ReturnRefOfCopy(full_path)); EXPECT_CALL(*download_file, Detach()); item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED); + DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, + 0, + scoped_ptr<crypto::SecureHash>()); ASSERT_TRUE(item->IsDangerous()); EXPECT_CALL(*mock_delegate(), DownloadRemoved(_)); @@ -1306,7 +1608,9 @@ TEST_F(DownloadItemTest, StealInterruptedNonResumableDangerousDownload) { DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE); EXPECT_CALL(*download_file, Cancel()); item->DestinationObserverAsWeakPtr()->DestinationError( - DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); + DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, + 0, + scoped_ptr<crypto::SecureHash>()); ASSERT_TRUE(item->IsDangerous()); EXPECT_CALL(*mock_delegate(), DownloadRemoved(_)); @@ -1319,6 +1623,428 @@ TEST_F(DownloadItemTest, StealInterruptedNonResumableDangerousDownload) { EXPECT_TRUE(returned_path.empty()); } +namespace { + +// The DownloadItemDestinationUpdateRaceTest fixture (defined below) is used to +// test for race conditions between download destination events received via the +// DownloadDestinationObserver interface, and the target determination logic. +// +// The general control flow for DownloadItemImpl looks like this: +// +// * Start() called, which in turn calls DownloadFile::Initialize(). +// +// Even though OnDownloadFileInitialized hasn't been called, there could now +// be destination observer calls queued prior to the task that calls +// OnDownloadFileInitialized. Let's call this point in the workflow "A". +// +// * DownloadItemImpl::OnDownloadFileInitialized() called. +// +// * Assuming the result is successful, DII now invokes the delegate's +// DetermineDownloadTarget method. +// +// At this point DonwnloadFile acts as the source of +// DownloadDestinationObserver events, and may invoke callbacks. Let's call +// this point in the workflow "B". +// +// * DII::OnDownloadTargetDetermined() invoked after delegate is done with +// target determination. +// +// * DII attempts to rename the DownloadFile to its intermediate name. +// +// More DownloadDestinationObserver events can happen here. Let's call this +// point in the workflow "C". +// +// * DII::OnDownloadRenamedToIntermediateName() invoked. Assuming all went well, +// DII is now in IN_PROGRESS state. +// +// More DownloadDestinationObserver events can happen here. Let's call this +// point in the workflow "D". +// +// The DownloadItemDestinationUpdateRaceTest works by generating various +// combinations of DownloadDestinationObserver events that might occur at the +// points "A", "B", "C", and "D" above. Each test in this suite cranks a +// DownloadItemImpl through the states listed above and invokes the events +// assigned to each position. + +// This type of callback represents a call to a DownloadDestinationObserver +// method that's missing the DownloadDestinationObserver object. Currying this +// way allows us to bind a call prior to constructing the object on which the +// method would be invoked. This is necessary since we are going to construct +// various permutations of observer calls that will then be applied to a +// DownloadItem in a state as yet undetermined. +using CurriedObservation = + base::Callback<void(base::WeakPtr<DownloadDestinationObserver>)>; + +// A list of observations that are to be made during some event in the +// DownloadItemImpl control flow. Ordering of the observations is significant. +using ObservationList = std::deque<CurriedObservation>; + +// An ordered list of events. +// +// An "event" in this context refers to some stage in the DownloadItemImpl's +// workflow described as "A", "B", "C", or "D" above. An EventList is expected +// to always contains kEventCount events. +using EventList = std::deque<ObservationList>; + +// Number of events in an EventList. This is always 4 for now as described +// above. +const int kEventCount = 4; + +// The following functions help us with currying the calls to +// DownloadDestinationObserver. If std::bind was allowed along with +// std::placeholders, it is possible to avoid these functions, but currently +// Chromium doesn't allow using std::bind for good reasons. +void DestinationUpdateInvoker( + int64_t bytes_so_far, + int64_t bytes_per_sec, + base::WeakPtr<DownloadDestinationObserver> observer) { + DVLOG(20) << "DestinationUpdate(bytes_so_far:" << bytes_so_far + << ", bytes_per_sec:" << bytes_per_sec + << ") observer:" << !!observer; + if (observer) + observer->DestinationUpdate(bytes_so_far, bytes_per_sec); +} + +void DestinationErrorInvoker( + DownloadInterruptReason reason, + int64_t bytes_so_far, + base::WeakPtr<DownloadDestinationObserver> observer) { + DVLOG(20) << "DestinationError(reason:" + << DownloadInterruptReasonToString(reason) + << ", bytes_so_far:" << bytes_so_far << ") observer:" << !!observer; + if (observer) + observer->DestinationError( + reason, bytes_so_far, scoped_ptr<crypto::SecureHash>()); +} + +void DestinationCompletedInvoker( + int64_t total_bytes, + base::WeakPtr<DownloadDestinationObserver> observer) { + DVLOG(20) << "DestinationComplete(total_bytes:" << total_bytes + << ") observer:" << !!observer; + if (observer) + observer->DestinationCompleted(total_bytes, + scoped_ptr<crypto::SecureHash>()); +} + +// Given a set of observations (via the range |begin|..|end|), constructs a list +// of EventLists such that: +// +// * There are exactly |event_count| ObservationSets in each EventList. +// +// * Each ObservationList in each EventList contains a subrange (possibly empty) +// of observations from the input range, in the same order as the input range. +// +// * The ordering of the ObservationList in each EventList is such that all +// observations in one ObservationList occur earlier than all observations in +// an ObservationList that follows it. +// +// * The list of EventLists together describe all the possible ways in which the +// list of observations can be distributed into |event_count| events. +std::vector<EventList> DistributeObservationsIntoEvents( + const std::vector<CurriedObservation>::iterator begin, + const std::vector<CurriedObservation>::iterator end, + int event_count) { + std::vector<EventList> all_event_lists; + for (auto partition = begin;; ++partition) { + ObservationList first_group_of_observations(begin, partition); + if (event_count > 1) { + std::vector<EventList> list_of_subsequent_events = + DistributeObservationsIntoEvents(partition, end, event_count - 1); + for (const auto& subsequent_events : list_of_subsequent_events) { + EventList event_list; + event_list = subsequent_events; + event_list.push_front(first_group_of_observations); + all_event_lists.push_back(event_list); + } + } else { + EventList event_list; + event_list.push_front(first_group_of_observations); + all_event_lists.push_back(event_list); + } + if (partition == end) + break; + } + return all_event_lists; +} + +// For the purpose of this tests, we are only concerned with 3 events: +// +// 1. Immediately after the DownloadFile is initialized. +// 2. Immediately after the DownloadTargetCallback is invoked. +// 3. Immediately after the intermediate file is renamed. +// +// We are going to take a couple of sets of DownloadDestinationObserver events +// and distribute them into the three events described above. And then we are +// going to invoke the observations while a DownloadItemImpl is carefully +// stepped through its stages. + +std::vector<EventList> GenerateSuccessfulEventLists() { + std::vector<CurriedObservation> all_observations; + all_observations.push_back(base::Bind(&DestinationUpdateInvoker, 100, 100)); + all_observations.push_back(base::Bind(&DestinationUpdateInvoker, 200, 100)); + all_observations.push_back(base::Bind(&DestinationCompletedInvoker, 200)); + return DistributeObservationsIntoEvents(all_observations.begin(), + all_observations.end(), kEventCount); +} + +std::vector<EventList> GenerateFailingEventLists() { + std::vector<CurriedObservation> all_observations; + all_observations.push_back(base::Bind(&DestinationUpdateInvoker, 100, 100)); + all_observations.push_back(base::Bind( + &DestinationErrorInvoker, DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, 100)); + return DistributeObservationsIntoEvents(all_observations.begin(), + all_observations.end(), kEventCount); +} + +class DownloadItemDestinationUpdateRaceTest + : public DownloadItemTest, + public ::testing::WithParamInterface<EventList> { + public: + DownloadItemDestinationUpdateRaceTest() + : DownloadItemTest(), + item_(CreateDownloadItem()), + file_(new ::testing::StrictMock<MockDownloadFile>()), + request_handle_(new ::testing::StrictMock<MockRequestHandle>()) { + DCHECK_EQ(GetParam().size(), static_cast<unsigned>(kEventCount)); + EXPECT_CALL(*request_handle_, GetWebContents()) + .WillRepeatedly(Return(nullptr)); + } + + protected: + const ObservationList& PreInitializeFileObservations() { + return GetParam().front(); + } + const ObservationList& PostInitializeFileObservations() { + return *(GetParam().begin() + 1); + } + const ObservationList& PostTargetDeterminationObservations() { + return *(GetParam().begin() + 2); + } + const ObservationList& PostIntermediateRenameObservations() { + return *(GetParam().begin() + 3); + } + + // Apply all the observations in |observations| to |observer|, but do so + // asynchronously so that the events are applied in order behind any tasks + // that are already scheduled. + void ScheduleObservations( + const ObservationList& observations, + base::WeakPtr<DownloadDestinationObserver> observer) { + for (const auto action : observations) + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(action, observer)); + } + + DownloadItemImpl* item_; + scoped_ptr<MockDownloadFile> file_; + scoped_ptr<MockRequestHandle> request_handle_; + + std::queue<base::Closure> successful_update_events_; + std::queue<base::Closure> failing_update_events_; +}; + +INSTANTIATE_TEST_CASE_P(Success, + DownloadItemDestinationUpdateRaceTest, + ::testing::ValuesIn(GenerateSuccessfulEventLists())); + +INSTANTIATE_TEST_CASE_P(Failure, + DownloadItemDestinationUpdateRaceTest, + ::testing::ValuesIn(GenerateFailingEventLists())); + +} // namespace + +// Run through the DII workflow but the embedder cancels the download at target +// determination. +TEST_P(DownloadItemDestinationUpdateRaceTest, DownloadCancelledByUser) { + // Expect that the download file and the request will be cancelled as a + // result. + EXPECT_CALL(*file_, Cancel()); + EXPECT_CALL(*request_handle_, CancelRequest()); + + base::RunLoop download_start_loop; + DownloadFile::InitializeCallback initialize_callback; + EXPECT_CALL(*file_, Initialize(_)) + .WillOnce(DoAll(SaveArg<0>(&initialize_callback), + ScheduleClosure(download_start_loop.QuitClosure()))); + item_->Start(std::move(file_), std::move(request_handle_), *create_info()); + download_start_loop.Run(); + + base::WeakPtr<DownloadDestinationObserver> destination_observer = + item_->DestinationObserverAsWeakPtr(); + + ScheduleObservations(PreInitializeFileObservations(), destination_observer); + RunAllPendingInMessageLoops(); + + base::RunLoop initialize_completion_loop; + DownloadItemImplDelegate::DownloadTargetCallback target_callback; + EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(_, _)) + .WillOnce( + DoAll(SaveArg<1>(&target_callback), + ScheduleClosure(initialize_completion_loop.QuitClosure()))); + ScheduleObservations(PostInitializeFileObservations(), destination_observer); + initialize_callback.Run(DOWNLOAD_INTERRUPT_REASON_NONE); + initialize_completion_loop.Run(); + + RunAllPendingInMessageLoops(); + + ASSERT_FALSE(target_callback.is_null()); + ScheduleObservations(PostTargetDeterminationObservations(), + destination_observer); + target_callback.Run(base::FilePath(), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, base::FilePath()); + EXPECT_EQ(DownloadItem::CANCELLED, item_->GetState()); + RunAllPendingInMessageLoops(); +} + +// Run through the DII workflow, but the intermediate rename fails. +TEST_P(DownloadItemDestinationUpdateRaceTest, IntermediateRenameFails) { + // Expect that the download file and the request will be cancelled as a + // result. + EXPECT_CALL(*file_, Cancel()); + EXPECT_CALL(*request_handle_, CancelRequest()); + + // Intermediate rename loop is not used immediately, but let's set up the + // DownloadFile expectations since we are about to transfer its ownership to + // the DownloadItem. + base::RunLoop intermediate_rename_loop; + DownloadFile::RenameCompletionCallback intermediate_rename_callback; + EXPECT_CALL(*file_, RenameAndUniquify(_, _)) + .WillOnce(DoAll(SaveArg<1>(&intermediate_rename_callback), + ScheduleClosure(intermediate_rename_loop.QuitClosure()))); + + base::RunLoop download_start_loop; + DownloadFile::InitializeCallback initialize_callback; + EXPECT_CALL(*file_, Initialize(_)) + .WillOnce(DoAll(SaveArg<0>(&initialize_callback), + ScheduleClosure(download_start_loop.QuitClosure()))); + + item_->Start(std::move(file_), std::move(request_handle_), *create_info()); + download_start_loop.Run(); + base::WeakPtr<DownloadDestinationObserver> destination_observer = + item_->DestinationObserverAsWeakPtr(); + + ScheduleObservations(PreInitializeFileObservations(), destination_observer); + RunAllPendingInMessageLoops(); + + base::RunLoop initialize_completion_loop; + DownloadItemImplDelegate::DownloadTargetCallback target_callback; + EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(_, _)) + .WillOnce( + DoAll(SaveArg<1>(&target_callback), + ScheduleClosure(initialize_completion_loop.QuitClosure()))); + ScheduleObservations(PostInitializeFileObservations(), destination_observer); + initialize_callback.Run(DOWNLOAD_INTERRUPT_REASON_NONE); + initialize_completion_loop.Run(); + + RunAllPendingInMessageLoops(); + ASSERT_FALSE(target_callback.is_null()); + + ScheduleObservations(PostTargetDeterminationObservations(), + destination_observer); + target_callback.Run(base::FilePath(kDummyTargetPath), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + base::FilePath(kDummyIntermediatePath)); + + intermediate_rename_loop.Run(); + ASSERT_FALSE(intermediate_rename_callback.is_null()); + + ScheduleObservations(PostIntermediateRenameObservations(), + destination_observer); + intermediate_rename_callback.Run(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, + base::FilePath()); + RunAllPendingInMessageLoops(); + + EXPECT_EQ(DownloadItem::INTERRUPTED, item_->GetState()); +} + +// Run through the DII workflow. Download file initialization, target +// determination and intermediate rename all succeed. +TEST_P(DownloadItemDestinationUpdateRaceTest, IntermediateRenameSucceeds) { + // We expect either that the download will fail (in which case the request and + // the download file will be cancelled), or it will succeed (in which case the + // DownloadFile will Detach()). It depends on the list of observations that + // are given to us. + EXPECT_CALL(*file_, Cancel()).Times(::testing::AnyNumber()); + EXPECT_CALL(*request_handle_, CancelRequest()).Times(::testing::AnyNumber()); + EXPECT_CALL(*file_, Detach()).Times(::testing::AnyNumber()); + + EXPECT_CALL(*file_, FullPath()) + .WillRepeatedly(ReturnRefOfCopy(base::FilePath(kDummyIntermediatePath))); + + // Intermediate rename loop is not used immediately, but let's set up the + // DownloadFile expectations since we are about to transfer its ownership to + // the DownloadItem. + base::RunLoop intermediate_rename_loop; + DownloadFile::RenameCompletionCallback intermediate_rename_callback; + EXPECT_CALL(*file_, RenameAndUniquify(_, _)) + .WillOnce(DoAll(SaveArg<1>(&intermediate_rename_callback), + ScheduleClosure(intermediate_rename_loop.QuitClosure()))); + + base::RunLoop download_start_loop; + DownloadFile::InitializeCallback initialize_callback; + EXPECT_CALL(*file_, Initialize(_)) + .WillOnce(DoAll(SaveArg<0>(&initialize_callback), + ScheduleClosure(download_start_loop.QuitClosure()))); + + item_->Start(std::move(file_), std::move(request_handle_), *create_info()); + download_start_loop.Run(); + base::WeakPtr<DownloadDestinationObserver> destination_observer = + item_->DestinationObserverAsWeakPtr(); + + ScheduleObservations(PreInitializeFileObservations(), destination_observer); + RunAllPendingInMessageLoops(); + + base::RunLoop initialize_completion_loop; + DownloadItemImplDelegate::DownloadTargetCallback target_callback; + EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(_, _)) + .WillOnce( + DoAll(SaveArg<1>(&target_callback), + ScheduleClosure(initialize_completion_loop.QuitClosure()))); + ScheduleObservations(PostInitializeFileObservations(), destination_observer); + initialize_callback.Run(DOWNLOAD_INTERRUPT_REASON_NONE); + initialize_completion_loop.Run(); + + RunAllPendingInMessageLoops(); + ASSERT_FALSE(target_callback.is_null()); + + ScheduleObservations(PostTargetDeterminationObservations(), + destination_observer); + target_callback.Run(base::FilePath(kDummyTargetPath), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + base::FilePath(kDummyIntermediatePath)); + + intermediate_rename_loop.Run(); + ASSERT_FALSE(intermediate_rename_callback.is_null()); + + // This may or may not be called, depending on whether there are any errors in + // our action list. + EXPECT_CALL(*mock_delegate(), ShouldCompleteDownload(_, _)) + .Times(::testing::AnyNumber()); + + ScheduleObservations(PostIntermediateRenameObservations(), + destination_observer); + intermediate_rename_callback.Run(DOWNLOAD_INTERRUPT_REASON_NONE, + base::FilePath(kDummyIntermediatePath)); + RunAllPendingInMessageLoops(); + + // The state of the download depends on the observer events that were played + // back to the DownloadItemImpl. Hence we can't establish a single expectation + // here. On Debug builds, the DCHECKs will verify that the state transitions + // were correct. On Release builds, tests are expected to run to completion + // without crashing on success. + EXPECT_TRUE(item_->GetState() == DownloadItem::IN_PROGRESS || + item_->GetState() == DownloadItem::INTERRUPTED); + if (item_->GetState() == DownloadItem::INTERRUPTED) + EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, item_->GetLastReason()); + + item_->Cancel(true); + RunAllPendingInMessageLoops(); +} + TEST(MockDownloadItem, Compiles) { MockDownloadItem mock_item; } diff --git a/chromium/content/browser/download/download_manager_impl.cc b/chromium/content/browser/download/download_manager_impl.cc index 97c0a85e714..28f1c3fca65 100644 --- a/chromium/content/browser/download/download_manager_impl.cc +++ b/chromium/content/browser/download/download_manager_impl.cc @@ -45,6 +45,7 @@ #include "net/base/request_priority.h" #include "net/base/upload_bytes_element_reader.h" #include "net/url_request/url_request_context.h" +#include "storage/browser/blob/blob_url_request_job_factory.h" #include "url/origin.h" namespace content { @@ -55,90 +56,53 @@ scoped_ptr<UrlDownloader, BrowserThread::DeleteOnIOThread> BeginDownload( uint32_t download_id, base::WeakPtr<DownloadManagerImpl> download_manager) { DCHECK_CURRENTLY_ON(BrowserThread::IO); - // ResourceDispatcherHost{Base} is-not-a URLRequest::Delegate, and - // DownloadUrlParameters can-not include resource_dispatcher_host_impl.h, so - // we must down cast. RDHI is the only subclass of RDH as of 2012 May 4. - scoped_ptr<net::URLRequest> request( - params->resource_context()->GetRequestContext()->CreateRequest( - params->url(), net::DEFAULT_PRIORITY, NULL)); - request->set_method(params->method()); - if (!params->post_body().empty()) { - const std::string& body = params->post_body(); - scoped_ptr<net::UploadElementReader> reader( - net::UploadOwnedBytesElementReader::CreateWithString(body)); - request->set_upload( - net::ElementsUploadDataStream::CreateWithReader(std::move(reader), 0)); - } - if (params->post_id() >= 0) { - // The POST in this case does not have an actual body, and only works - // when retrieving data from cache. This is done because we don't want - // to do a re-POST without user consent, and currently don't have a good - // plan on how to display the UI for that. - DCHECK(params->prefer_cache()); - DCHECK_EQ("POST", params->method()); - std::vector<scoped_ptr<net::UploadElementReader>> element_readers; - request->set_upload(make_scoped_ptr(new net::ElementsUploadDataStream( - std::move(element_readers), params->post_id()))); - } - // If we're not at the beginning of the file, retrieve only the remaining - // portion. - bool has_last_modified = !params->last_modified().empty(); - bool has_etag = !params->etag().empty(); - - // If we've asked for a range, we want to make sure that we only - // get that range if our current copy of the information is good. - // We shouldn't be asked to continue if we don't have a verifier. - DCHECK(params->offset() == 0 || has_etag || has_last_modified); - - if (params->offset() > 0 && (has_etag || has_last_modified)) { - request->SetExtraRequestHeaderByName( - "Range", - base::StringPrintf("bytes=%" PRId64 "-", params->offset()), - true); - - // In accordance with RFC 2616 Section 14.27, use If-Range to specify that - // the server return the entire entity if the validator doesn't match. - // Last-Modified can be used in the absence of ETag as a validator if the - // response headers satisfied the HttpUtil::HasStrongValidators() predicate. - // - // This function assumes that HasStrongValidators() was true and that the - // ETag and Last-Modified header values supplied are valid. - request->SetExtraRequestHeaderByName( - "If-Range", has_etag ? params->etag() : params->last_modified(), true); + scoped_ptr<net::URLRequest> url_request = + DownloadRequestCore::CreateRequestOnIOThread(download_id, params.get()); + scoped_ptr<storage::BlobDataHandle> blob_data_handle = + params->GetBlobDataHandle(); + if (blob_data_handle) { + storage::BlobProtocolHandler::SetRequestedBlobDataHandle( + url_request.get(), std::move(blob_data_handle)); } - for (DownloadUrlParameters::RequestHeadersType::const_iterator iter - = params->request_headers_begin(); - iter != params->request_headers_end(); - ++iter) { - request->SetExtraRequestHeaderByName( - iter->first, iter->second, false /*overwrite*/); - } - - scoped_ptr<DownloadSaveInfo> save_info(new DownloadSaveInfo()); - save_info->file_path = params->file_path(); - save_info->suggested_name = params->suggested_name(); - save_info->offset = params->offset(); - save_info->hash_state = params->hash_state(); - save_info->prompt_for_save_location = params->prompt(); - save_info->file = params->GetFile(); - + // If there's a valid renderer process associated with the request, then the + // request should be driven by the ResourceLoader. Pass it over to the + // ResourceDispatcherHostImpl which will in turn pass it along to the + // ResourceLoader. if (params->render_process_host_id() != -1) { - ResourceDispatcherHost::Get()->BeginDownload( - std::move(request), params->referrer(), params->content_initiated(), - params->resource_context(), params->render_process_host_id(), - params->render_view_host_routing_id(), - params->render_frame_host_routing_id(), params->prefer_cache(), - params->do_not_prompt_for_login(), std::move(save_info), download_id, - params->callback()); + DownloadInterruptReason reason = + ResourceDispatcherHostImpl::Get()->BeginDownload( + std::move(url_request), params->referrer(), + params->content_initiated(), params->resource_context(), + params->render_process_host_id(), + params->render_view_host_routing_id(), + params->render_frame_host_routing_id(), + params->do_not_prompt_for_login()); + + // If the download was accepted, the DownloadResourceHandler is now + // responsible for driving the request to completion. + if (reason == DOWNLOAD_INTERRUPT_REASON_NONE) + return nullptr; + + // Otherwise, create an interrupted download. + scoped_ptr<DownloadCreateInfo> failed_created_info( + new DownloadCreateInfo(base::Time::Now(), net::BoundNetLog(), + make_scoped_ptr(new DownloadSaveInfo))); + failed_created_info->url_chain.push_back(params->url()); + failed_created_info->result = reason; + scoped_ptr<ByteStreamReader> empty_byte_stream; + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&DownloadManager::StartDownload, download_manager, + base::Passed(&failed_created_info), + base::Passed(&empty_byte_stream), params->callback())); return nullptr; } + return scoped_ptr<UrlDownloader, BrowserThread::DeleteOnIOThread>( - UrlDownloader::BeginDownload(download_manager, std::move(request), - params->referrer(), params->prefer_cache(), - std::move(save_info), download_id, - params->callback()) + UrlDownloader::BeginDownload(download_manager, std::move(url_request), + params->referrer()) .release()); } @@ -149,6 +113,7 @@ class DownloadItemFactoryImpl : public DownloadItemFactory { DownloadItemImpl* CreatePersistedItem( DownloadItemImplDelegate* delegate, + const std::string& guid, uint32_t download_id, const base::FilePath& current_path, const base::FilePath& target_path, @@ -162,31 +127,33 @@ class DownloadItemFactoryImpl : public DownloadItemFactory { const std::string& last_modified, int64_t received_bytes, int64_t total_bytes, + const std::string& hash, DownloadItem::DownloadState state, DownloadDangerType danger_type, DownloadInterruptReason interrupt_reason, bool opened, const net::BoundNetLog& bound_net_log) override { - return new DownloadItemImpl( - delegate, - download_id, - current_path, - target_path, - url_chain, - referrer_url, - mime_type, - original_mime_type, - start_time, - end_time, - etag, - last_modified, - received_bytes, - total_bytes, - state, - danger_type, - interrupt_reason, - opened, - bound_net_log); + return new DownloadItemImpl(delegate, + guid, + download_id, + current_path, + target_path, + url_chain, + referrer_url, + mime_type, + original_mime_type, + start_time, + end_time, + etag, + last_modified, + received_bytes, + total_bytes, + hash, + state, + danger_type, + interrupt_reason, + opened, + bound_net_log); } DownloadItemImpl* CreateActiveItem( @@ -240,6 +207,7 @@ DownloadItemImpl* DownloadManagerImpl::CreateActiveItem( DownloadItemImpl* download = item_factory_->CreateActiveItem(this, id, info, bound_net_log); downloads_[id] = download; + downloads_by_guid_[download->GetGuid()] = download; return download; } @@ -322,13 +290,13 @@ void DownloadManagerImpl::Shutdown() { // dangerous downloads which will remain in history if they aren't explicitly // accepted or discarded. Canceling will remove the intermediate download // file. - for (DownloadMap::iterator it = downloads_.begin(); it != downloads_.end(); - ++it) { - DownloadItemImpl* download = it->second; + for (const auto& it : downloads_) { + DownloadItemImpl* download = it.second; if (download->GetState() == DownloadItem::IN_PROGRESS) download->Cancel(false); } STLDeleteValues(&downloads_); + downloads_by_guid_.clear(); url_downloaders_.clear(); // We'll have nothing more to report to the observers after this point. @@ -345,6 +313,11 @@ void DownloadManagerImpl::StartDownload( const DownloadUrlParameters::OnStartedCallback& on_started) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(info); + // |stream| is only non-nil if the download request was successful. + DCHECK((info->result == DOWNLOAD_INTERRUPT_REASON_NONE && stream.get()) || + (info->result != DOWNLOAD_INTERRUPT_REASON_NONE && !stream.get())); + DVLOG(20) << __FUNCTION__ << "()" + << " result=" << DownloadInterruptReasonToString(info->result); uint32_t download_id = info->download_id; const bool new_download = (download_id == content::DownloadItem::kInvalidId); base::Callback<void(uint32_t)> got_id(base::Bind( @@ -380,13 +353,12 @@ void DownloadManagerImpl::StartDownloadWithId( if (!on_started.is_null()) on_started.Run(NULL, DOWNLOAD_INTERRUPT_REASON_USER_CANCELED); // The ByteStreamReader lives and dies on the FILE thread. - BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, - stream.release()); + if (info->result == DOWNLOAD_INTERRUPT_REASON_NONE) + BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, + stream.release()); return; } download = item_iterator->second; - DCHECK_EQ(download->GetState(), DownloadItem::IN_PROGRESS); - download->MergeOriginInfoOnResume(*info); } base::FilePath default_download_directory; @@ -397,20 +369,23 @@ void DownloadManagerImpl::StartDownloadWithId( &default_download_directory, &skip_dir_check); } - // Create the download file and start the download. - scoped_ptr<DownloadFile> download_file(file_factory_->CreateFile( - std::move(info->save_info), default_download_directory, info->url(), - info->referrer_url, delegate_ && delegate_->GenerateFileHash(), - std::move(stream), download->GetBoundNetLog(), - download->DestinationObserverAsWeakPtr())); - - // Attach the client ID identifying the app to the AV system. - if (download_file.get() && delegate_) { - download_file->SetClientGuid( - delegate_->ApplicationClientIdForFileScanning()); + scoped_ptr<DownloadFile> download_file; + + if (info->result == DOWNLOAD_INTERRUPT_REASON_NONE) { + DCHECK(stream.get()); + download_file.reset( + file_factory_->CreateFile(std::move(info->save_info), + default_download_directory, + std::move(stream), + download->GetBoundNetLog(), + download->DestinationObserverAsWeakPtr())); } + // It is important to leave info->save_info intact in the case of an interrupt + // so that the DownloadItem can salvage what it can out of a failed resumption + // attempt. - download->Start(std::move(download_file), std::move(info->request_handle)); + download->Start(std::move(download_file), std::move(info->request_handle), + *info); // For interrupted downloads, Start() will transition the state to // IN_PROGRESS and consumers will be notified via OnDownloadUpdated(). @@ -426,9 +401,8 @@ void DownloadManagerImpl::StartDownloadWithId( void DownloadManagerImpl::CheckForHistoryFilesRemoval() { DCHECK_CURRENTLY_ON(BrowserThread::UI); - for (DownloadMap::iterator it = downloads_.begin(); - it != downloads_.end(); ++it) { - DownloadItemImpl* item = it->second; + for (const auto& it : downloads_) { + DownloadItemImpl* item = it.second; CheckForFileRemoval(item); } } @@ -487,6 +461,8 @@ void DownloadManagerImpl::CreateSavePackageDownloadItemWithId( this, id, main_file_path, page_url, mime_type, std::move(request_handle), bound_net_log); downloads_[download_item->GetId()] = download_item; + DCHECK(!ContainsKey(downloads_by_guid_, download_item->GetGuid())); + downloads_by_guid_[download_item->GetGuid()] = download_item; FOR_EACH_OBSERVER(Observer, observers_, OnDownloadCreated( this, download_item)); if (!item_created.is_null()) @@ -532,6 +508,8 @@ void DownloadManagerImpl::DownloadRemoved(DownloadItemImpl* download) { if (!download) return; + downloads_by_guid_.erase(download->GetGuid()); + uint32_t download_id = download->GetId(); if (downloads_.erase(download_id) == 0) return; @@ -556,19 +534,18 @@ void DownloadManagerImpl::RemoveUrlDownloader(UrlDownloader* downloader) { namespace { -bool RemoveDownloadBetween(base::Time remove_begin, - base::Time remove_end, - const DownloadItemImpl* download_item) { - return download_item->GetStartTime() >= remove_begin && - (remove_end.is_null() || download_item->GetStartTime() < remove_end); +bool EmptyFilter(const GURL& url) { + return true; } -bool RemoveDownloadByOriginAndTime(const url::Origin& origin, - base::Time remove_begin, - base::Time remove_end, - const DownloadItemImpl* download_item) { - return origin.IsSameOriginWith(url::Origin(download_item->GetURL())) && - RemoveDownloadBetween(remove_begin, remove_end, download_item); +bool RemoveDownloadByURLAndTime( + const base::Callback<bool(const GURL&)>& url_filter, + base::Time remove_begin, + base::Time remove_end, + const DownloadItemImpl* download_item) { + return url_filter.Run(download_item->GetURL()) && + download_item->GetStartTime() >= remove_begin && + (remove_end.is_null() || download_item->GetStartTime() < remove_end); } } // namespace @@ -591,28 +568,21 @@ int DownloadManagerImpl::RemoveDownloads(const DownloadRemover& remover) { return count; } -int DownloadManagerImpl::RemoveDownloadsByOriginAndTime( - const url::Origin& origin, +int DownloadManagerImpl::RemoveDownloadsByURLAndTime( + const base::Callback<bool(const GURL&)>& url_filter, base::Time remove_begin, base::Time remove_end) { - return RemoveDownloads(base::Bind(&RemoveDownloadByOriginAndTime, - base::ConstRef(origin), remove_begin, - remove_end)); -} - -int DownloadManagerImpl::RemoveDownloadsBetween(base::Time remove_begin, - base::Time remove_end) { - return RemoveDownloads( - base::Bind(&RemoveDownloadBetween, remove_begin, remove_end)); -} - -int DownloadManagerImpl::RemoveDownloads(base::Time remove_begin) { - return RemoveDownloadsBetween(remove_begin, base::Time()); + return RemoveDownloads(base::Bind(&RemoveDownloadByURLAndTime, + url_filter, + remove_begin, remove_end)); } int DownloadManagerImpl::RemoveAllDownloads() { + const base::Callback<bool(const GURL&)> empty_filter = + base::Bind(&EmptyFilter); // The null times make the date range unbounded. - int num_deleted = RemoveDownloadsBetween(base::Time(), base::Time()); + int num_deleted = RemoveDownloadsByURLAndTime( + empty_filter, base::Time(), base::Time()); RecordClearAllSize(num_deleted); return num_deleted; } @@ -641,6 +611,7 @@ void DownloadManagerImpl::RemoveObserver(Observer* observer) { } DownloadItem* DownloadManagerImpl::CreateDownloadItem( + const std::string& guid, uint32_t id, const base::FilePath& current_path, const base::FilePath& target_path, @@ -654,6 +625,7 @@ DownloadItem* DownloadManagerImpl::CreateDownloadItem( const std::string& last_modified, int64_t received_bytes, int64_t total_bytes, + const std::string& hash, DownloadItem::DownloadState state, DownloadDangerType danger_type, DownloadInterruptReason interrupt_reason, @@ -662,8 +634,10 @@ DownloadItem* DownloadManagerImpl::CreateDownloadItem( NOTREACHED(); return NULL; } + DCHECK(!ContainsKey(downloads_by_guid_, guid)); DownloadItemImpl* item = item_factory_->CreatePersistedItem( this, + guid, id, current_path, target_path, @@ -677,12 +651,14 @@ DownloadItem* DownloadManagerImpl::CreateDownloadItem( last_modified, received_bytes, total_bytes, + hash, state, danger_type, interrupt_reason, opened, net::BoundNetLog::Make(net_log_, net::NetLog::SOURCE_DOWNLOAD)); downloads_[id] = item; + downloads_by_guid_[guid] = item; FOR_EACH_OBSERVER(Observer, observers_, OnDownloadCreated(this, item)); DVLOG(20) << __FUNCTION__ << "() download = " << item->DebugString(true); return item; @@ -690,9 +666,8 @@ DownloadItem* DownloadManagerImpl::CreateDownloadItem( int DownloadManagerImpl::InProgressCount() const { int count = 0; - for (DownloadMap::const_iterator it = downloads_.begin(); - it != downloads_.end(); ++it) { - if (it->second->GetState() == DownloadItem::IN_PROGRESS) + for (const auto& it : downloads_) { + if (it.second->GetState() == DownloadItem::IN_PROGRESS) ++count; } return count; @@ -700,13 +675,12 @@ int DownloadManagerImpl::InProgressCount() const { int DownloadManagerImpl::NonMaliciousInProgressCount() const { int count = 0; - for (DownloadMap::const_iterator it = downloads_.begin(); - it != downloads_.end(); ++it) { - if (it->second->GetState() == DownloadItem::IN_PROGRESS && - it->second->GetDangerType() != DOWNLOAD_DANGER_TYPE_DANGEROUS_URL && - it->second->GetDangerType() != DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT && - it->second->GetDangerType() != DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST && - it->second->GetDangerType() != + for (const auto& it : downloads_) { + if (it.second->GetState() == DownloadItem::IN_PROGRESS && + it.second->GetDangerType() != DOWNLOAD_DANGER_TYPE_DANGEROUS_URL && + it.second->GetDangerType() != DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT && + it.second->GetDangerType() != DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST && + it.second->GetDangerType() != DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED) { ++count; } @@ -715,21 +689,26 @@ int DownloadManagerImpl::NonMaliciousInProgressCount() const { } DownloadItem* DownloadManagerImpl::GetDownload(uint32_t download_id) { - return ContainsKey(downloads_, download_id) ? downloads_[download_id] : NULL; + return ContainsKey(downloads_, download_id) ? downloads_[download_id] + : nullptr; +} + +DownloadItem* DownloadManagerImpl::GetDownloadByGuid(const std::string& guid) { + DCHECK(guid == base::ToUpperASCII(guid)); + return ContainsKey(downloads_by_guid_, guid) ? downloads_by_guid_[guid] + : nullptr; } void DownloadManagerImpl::GetAllDownloads(DownloadVector* downloads) { - for (DownloadMap::iterator it = downloads_.begin(); - it != downloads_.end(); ++it) { - downloads->push_back(it->second); + for (const auto& it : downloads_) { + downloads->push_back(it.second); } } void DownloadManagerImpl::OpenDownload(DownloadItemImpl* download) { int num_unopened = 0; - for (DownloadMap::iterator it = downloads_.begin(); - it != downloads_.end(); ++it) { - DownloadItemImpl* item = it->second; + for (const auto& it : downloads_) { + DownloadItemImpl* item = it.second; if ((item->GetState() == DownloadItem::COMPLETE) && !item->GetOpened()) ++num_unopened; diff --git a/chromium/content/browser/download/download_manager_impl.h b/chromium/content/browser/download/download_manager_impl.h index ebac07ab081..610b76408fc 100644 --- a/chromium/content/browser/download/download_manager_impl.h +++ b/chromium/content/browser/download/download_manager_impl.h @@ -7,13 +7,12 @@ #include <stdint.h> -#include <map> #include <set> #include <string> +#include <unordered_map> #include <vector> #include "base/callback_forward.h" -#include "base/containers/hash_tables.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" @@ -42,7 +41,7 @@ class DownloadRequestHandleInterface; class CONTENT_EXPORT DownloadManagerImpl : public DownloadManager, private DownloadItemImplDelegate { public: - typedef base::Callback<void(DownloadItemImpl*)> DownloadItemImplCreated; + using DownloadItemImplCreated = base::Callback<void(DownloadItemImpl*)>; // Caller guarantees that |net_log| will remain valid // for the lifetime of DownloadManagerImpl (until Shutdown() is called). @@ -74,17 +73,16 @@ class CONTENT_EXPORT DownloadManagerImpl : public DownloadManager, scoped_ptr<ByteStreamReader> stream, const DownloadUrlParameters::OnStartedCallback& on_started) override; - int RemoveDownloadsByOriginAndTime(const url::Origin& origin, - base::Time remove_begin, - base::Time remove_end) override; - int RemoveDownloadsBetween(base::Time remove_begin, - base::Time remove_end) override; - int RemoveDownloads(base::Time remove_begin) override; + int RemoveDownloadsByURLAndTime( + const base::Callback<bool(const GURL&)>& url_filter, + base::Time remove_begin, + base::Time remove_end) override; int RemoveAllDownloads() override; void DownloadUrl(scoped_ptr<DownloadUrlParameters> params) override; void AddObserver(Observer* observer) override; void RemoveObserver(Observer* observer) override; content::DownloadItem* CreateDownloadItem( + const std::string& guid, uint32_t id, const base::FilePath& current_path, const base::FilePath& target_path, @@ -98,6 +96,7 @@ class CONTENT_EXPORT DownloadManagerImpl : public DownloadManager, const std::string& last_modified, int64_t received_bytes, int64_t total_bytes, + const std::string& hash, content::DownloadItem::DownloadState state, DownloadDangerType danger_type, DownloadInterruptReason interrupt_reason, @@ -107,6 +106,7 @@ class CONTENT_EXPORT DownloadManagerImpl : public DownloadManager, BrowserContext* GetBrowserContext() const override; void CheckForHistoryFilesRemoval() override; DownloadItem* GetDownload(uint32_t id) override; + DownloadItem* GetDownloadByGuid(const std::string& guid) override; // For testing; specifically, accessed from TestFileErrorInjector. void SetDownloadItemFactoryForTesting( @@ -118,10 +118,11 @@ class CONTENT_EXPORT DownloadManagerImpl : public DownloadManager, void RemoveUrlDownloader(UrlDownloader* downloader); private: - typedef std::set<DownloadItem*> DownloadSet; - typedef base::hash_map<uint32_t, DownloadItemImpl*> DownloadMap; - typedef std::vector<DownloadItemImpl*> DownloadItemImplVector; - typedef base::Callback<bool(const DownloadItemImpl*)> DownloadRemover; + using DownloadSet = std::set<DownloadItem*>; + using DownloadMap = std::unordered_map<uint32_t, DownloadItemImpl*>; + using DownloadGuidMap = std::unordered_map<std::string, DownloadItemImpl*>; + using DownloadItemImplVector = std::vector<DownloadItemImpl*>; + using DownloadRemover = base::Callback<bool(const DownloadItemImpl*)>; // For testing. friend class DownloadManagerTest; @@ -189,8 +190,16 @@ class CONTENT_EXPORT DownloadManagerImpl : public DownloadManager, // DownloadManager. This includes downloads started by the user in // this session, downloads initialized from the history system, and // "save page as" downloads. + // TODO(asanka): Remove this container in favor of downloads_by_guid_ as a + // part of http://crbug.com/593020. DownloadMap downloads_; + // Same as the above, but maps from GUID to download item. Note that the + // container is case sensitive. Hence the key needs to be normalized to + // upper-case when inserting new elements here. Fortunately for us, + // DownloadItemImpl already normalizes the string GUID. + DownloadGuidMap downloads_by_guid_; + int history_size_; // True if the download manager has been initialized and requires a shutdown. diff --git a/chromium/content/browser/download/download_manager_impl_unittest.cc b/chromium/content/browser/download/download_manager_impl_unittest.cc index 78d13863470..c72a1d07a82 100644 --- a/chromium/content/browser/download/download_manager_impl_unittest.cc +++ b/chromium/content/browser/download/download_manager_impl_unittest.cc @@ -6,12 +6,14 @@ #include <stddef.h> #include <stdint.h> + #include <set> #include <string> #include <utility> #include "base/bind.h" #include "base/files/scoped_temp_dir.h" +#include "base/guid.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" @@ -37,7 +39,6 @@ #include "content/public/test/mock_download_item.h" #include "content/public/test/test_browser_context.h" #include "content/public/test/test_browser_thread.h" -#include "net/base/net_util.h" #include "net/log/net_log.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock_mutant.h" @@ -50,6 +51,7 @@ using ::testing::Eq; using ::testing::Ref; using ::testing::Return; using ::testing::ReturnRef; +using ::testing::ReturnRefOfCopy; using ::testing::SetArgPointee; using ::testing::StrictMock; using ::testing::_; @@ -76,26 +78,27 @@ class MockDownloadItemImpl : public DownloadItemImpl { public: // Use history constructor for minimal base object. explicit MockDownloadItemImpl(DownloadItemImplDelegate* delegate) - : DownloadItemImpl( - delegate, - content::DownloadItem::kInvalidId, - base::FilePath(), - base::FilePath(), - std::vector<GURL>(), - GURL(), - "application/octet-stream", - "application/octet-stream", - base::Time(), - base::Time(), - std::string(), - std::string(), - 0, - 0, - DownloadItem::COMPLETE, - DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - DOWNLOAD_INTERRUPT_REASON_NONE, - false, - net::BoundNetLog()) {} + : DownloadItemImpl(delegate, + std::string("7d122682-55b5-4a47-a253-36cadc3e5bee"), + content::DownloadItem::kInvalidId, + base::FilePath(), + base::FilePath(), + std::vector<GURL>(), + GURL(), + "application/octet-stream", + "application/octet-stream", + base::Time(), + base::Time(), + std::string(), + std::string(), + 0, + 0, + std::string(), + DownloadItem::COMPLETE, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + DOWNLOAD_INTERRUPT_REASON_NONE, + false, + net::BoundNetLog()) {} virtual ~MockDownloadItemImpl() {} MOCK_METHOD4(OnDownloadTargetDetermined, @@ -114,10 +117,13 @@ class MockDownloadItemImpl : public DownloadItemImpl { MOCK_METHOD3(UpdateProgress, void(int64_t, int64_t, const std::string&)); MOCK_METHOD1(Cancel, void(bool)); MOCK_METHOD0(MarkAsComplete, void()); - MOCK_METHOD1(OnAllDataSaved, void(const std::string&)); + void OnAllDataSaved(int64_t, scoped_ptr<crypto::SecureHash>) override { + NOTREACHED(); + } MOCK_METHOD0(OnDownloadedFileRemoved, void()); void Start(scoped_ptr<DownloadFile> download_file, - scoped_ptr<DownloadRequestHandleInterface> req_handle) override { + scoped_ptr<DownloadRequestHandleInterface> req_handle, + const DownloadCreateInfo& create_info) override { MockStart(download_file.get(), req_handle.get()); } @@ -153,6 +159,7 @@ class MockDownloadItemImpl : public DownloadItemImpl { MOCK_CONST_METHOD0(GetHashState, const std::string&()); MOCK_CONST_METHOD0(GetHash, const std::string&()); MOCK_CONST_METHOD0(GetId, uint32_t()); + MOCK_CONST_METHOD0(GetGuid, const std::string&()); MOCK_CONST_METHOD0(GetStartTime, base::Time()); MOCK_CONST_METHOD0(GetEndTime, base::Time()); MOCK_METHOD0(GetDownloadManager, DownloadManager*()); @@ -199,7 +206,6 @@ class MockDownloadManagerDelegate : public DownloadManagerDelegate { bool(DownloadItem*, const base::Closure&)); MOCK_METHOD2(ShouldOpenDownload, bool(DownloadItem*, const DownloadOpenDelayedCallback&)); - MOCK_METHOD0(GenerateFileHash, bool()); MOCK_METHOD4(GetSaveDir, void(BrowserContext*, base::FilePath*, base::FilePath*, bool*)); MOCK_METHOD5(ChooseSavePath, void( @@ -237,6 +243,7 @@ class MockDownloadItemFactory // Overridden methods from DownloadItemFactory. DownloadItemImpl* CreatePersistedItem( DownloadItemImplDelegate* delegate, + const std::string& guid, uint32_t download_id, const base::FilePath& current_path, const base::FilePath& target_path, @@ -250,6 +257,7 @@ class MockDownloadItemFactory const std::string& last_modofied, int64_t received_bytes, int64_t total_bytes, + const std::string& hash, DownloadItem::DownloadState state, DownloadDangerType danger_type, DownloadInterruptReason interrupt_reason, @@ -304,6 +312,7 @@ void MockDownloadItemFactory::RemoveItem(int id) { DownloadItemImpl* MockDownloadItemFactory::CreatePersistedItem( DownloadItemImplDelegate* delegate, + const std::string& guid, uint32_t download_id, const base::FilePath& current_path, const base::FilePath& target_path, @@ -317,6 +326,7 @@ DownloadItemImpl* MockDownloadItemFactory::CreatePersistedItem( const std::string& last_modified, int64_t received_bytes, int64_t total_bytes, + const std::string& hash, DownloadItem::DownloadState state, DownloadDangerType danger_type, DownloadInterruptReason interrupt_reason, @@ -327,6 +337,7 @@ DownloadItemImpl* MockDownloadItemFactory::CreatePersistedItem( new StrictMock<MockDownloadItemImpl>(&item_delegate_); EXPECT_CALL(*result, GetId()) .WillRepeatedly(Return(download_id)); + EXPECT_CALL(*result, GetGuid()).WillRepeatedly(ReturnRefOfCopy(guid)); items_[download_id] = result; return result; } @@ -342,6 +353,9 @@ DownloadItemImpl* MockDownloadItemFactory::CreateActiveItem( new StrictMock<MockDownloadItemImpl>(&item_delegate_); EXPECT_CALL(*result, GetId()) .WillRepeatedly(Return(download_id)); + EXPECT_CALL(*result, GetGuid()) + .WillRepeatedly( + ReturnRefOfCopy(base::ToUpperASCII(base::GenerateGUID()))); items_[download_id] = result; // Active items are created and then immediately are called to start @@ -378,32 +392,24 @@ class MockDownloadFileFactory virtual ~MockDownloadFileFactory() {} // Overridden method from DownloadFileFactory - MOCK_METHOD8(MockCreateFile, MockDownloadFile*( - const DownloadSaveInfo&, - const base::FilePath&, - const GURL&, const GURL&, bool, - ByteStreamReader*, - const net::BoundNetLog&, - base::WeakPtr<DownloadDestinationObserver>)); + MOCK_METHOD2(MockCreateFile, + MockDownloadFile*(const DownloadSaveInfo&, ByteStreamReader*)); virtual DownloadFile* CreateFile( scoped_ptr<DownloadSaveInfo> save_info, const base::FilePath& default_download_directory, - const GURL& url, - const GURL& referrer_url, - bool calculate_hash, - scoped_ptr<ByteStreamReader> stream, + scoped_ptr<ByteStreamReader> byte_stream, const net::BoundNetLog& bound_net_log, - base::WeakPtr<DownloadDestinationObserver> observer) { - return MockCreateFile(*save_info.get(), default_download_directory, url, - referrer_url, calculate_hash, - stream.get(), bound_net_log, observer); + base::WeakPtr<DownloadDestinationObserver> observer) override { + return MockCreateFile(*save_info, byte_stream.get()); } }; class MockBrowserContext : public BrowserContext { public: - MockBrowserContext() {} + MockBrowserContext() { + content::BrowserContext::Initialize(this, base::FilePath()); + } ~MockBrowserContext() {} MOCK_CONST_METHOD0(GetPath, base::FilePath()); @@ -411,8 +417,6 @@ class MockBrowserContext : public BrowserContext { ZoomLevelDelegate*(const base::FilePath&)); MOCK_CONST_METHOD0(IsOffTheRecord, bool()); MOCK_METHOD0(GetRequestContext, net::URLRequestContextGetter*()); - MOCK_METHOD1(GetRequestContextForRenderProcess, - net::URLRequestContextGetter*(int renderer_child_id)); MOCK_METHOD0(GetMediaRequestContext, net::URLRequestContextGetter*()); MOCK_METHOD1(GetMediaRequestContextForRenderProcess, @@ -429,6 +433,23 @@ class MockBrowserContext : public BrowserContext { MOCK_METHOD0(GetPermissionManager, PermissionManager*()); MOCK_METHOD0(GetBackgroundSyncController, BackgroundSyncController*()); + // Define these two methods to avoid a + // cannot access private member declared in class 'ScopedVector<net::URLRequestInterceptor>' + // build error if they're put in MOCK_METHOD. + net::URLRequestContextGetter* CreateRequestContext( + ProtocolHandlerMap* protocol_handlers, + URLRequestInterceptorScopedVector request_interceptors) override { + return nullptr; + } + + net::URLRequestContextGetter* CreateRequestContextForStoragePartition( + const base::FilePath& partition_path, + bool in_memory, + ProtocolHandlerMap* protocol_handlers, + URLRequestInterceptorScopedVector request_interceptors) override { + return nullptr; + } + scoped_ptr<ZoomLevelDelegate> CreateZoomLevelDelegate( const base::FilePath& path) override { return scoped_ptr<ZoomLevelDelegate>(CreateZoomLevelDelegateMock(path)); @@ -445,6 +466,14 @@ class MockDownloadManagerObserver : public DownloadManager::Observer { MOCK_METHOD2(SelectFileDialogDisplayed, void(DownloadManager*, int32_t)); }; +class MockByteStreamReader : public ByteStreamReader { + public: + virtual ~MockByteStreamReader() {} + MOCK_METHOD2(Read, StreamState(scoped_refptr<net::IOBuffer>*, size_t*)); + MOCK_CONST_METHOD0(GetStatus, int()); + MOCK_METHOD1(RegisterCallback, void(const base::Closure&)); +}; + } // namespace class DownloadManagerTest : public testing::Test { @@ -529,7 +558,7 @@ class DownloadManagerTest : public testing::Test { // we call Start on it immediately, so we need to set that expectation // in the factory. scoped_ptr<DownloadRequestHandleInterface> req_handle; - item.Start(scoped_ptr<DownloadFile>(), std::move(req_handle)); + item.Start(scoped_ptr<DownloadFile>(), std::move(req_handle), info); DCHECK(id < download_urls_.size()); EXPECT_CALL(item, GetURL()).WillRepeatedly(ReturnRef(download_urls_[id])); @@ -605,7 +634,7 @@ class DownloadManagerTest : public testing::Test { // Confirm the appropriate invocations occur when you start a download. TEST_F(DownloadManagerTest, StartDownload) { scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo); - scoped_ptr<ByteStreamReader> stream; + scoped_ptr<ByteStreamReader> stream(new MockByteStreamReader); uint32_t local_id(5); // Random value base::FilePath download_path(FILE_PATH_LITERAL("download/path")); @@ -618,16 +647,12 @@ TEST_F(DownloadManagerTest, StartDownload) { // Doing nothing will set the default download directory to null. EXPECT_CALL(GetMockDownloadManagerDelegate(), GetSaveDir(_, _, _, _)); - EXPECT_CALL(GetMockDownloadManagerDelegate(), GenerateFileHash()) - .WillOnce(Return(true)); EXPECT_CALL(GetMockDownloadManagerDelegate(), ApplicationClientIdForFileScanning()) .WillRepeatedly(Return("client-id")); MockDownloadFile* mock_file = new MockDownloadFile; - EXPECT_CALL(*mock_file, SetClientGuid("client-id")); EXPECT_CALL(*mock_download_file_factory_.get(), - MockCreateFile(Ref(*info->save_info.get()), _, _, _, true, - stream.get(), _, _)) + MockCreateFile(Ref(*info->save_info.get()), stream.get())) .WillOnce(Return(mock_file)); download_manager_->StartDownload(std::move(info), std::move(stream), @@ -707,8 +732,53 @@ TEST_F(DownloadManagerTest, RemoveAllDownloads) { // result in them being removed from the DownloadManager list. } -// Confirm that only downloads with same origin are removed. -TEST_F(DownloadManagerTest, RemoveSameOriginDownloads) { +TEST_F(DownloadManagerTest, GetDownloadByGuid) { + for (uint32_t i = 0; i < 4; ++i) + AddItemToManager(); + + MockDownloadItemImpl& item = GetMockDownloadItem(0); + DownloadItem* result = download_manager_->GetDownloadByGuid(item.GetGuid()); + ASSERT_TRUE(result); + ASSERT_EQ(static_cast<DownloadItem*>(&item), result); + + ASSERT_FALSE(download_manager_->GetDownloadByGuid("")); + + const char kGuid[] = "8DF158E8-C980-4618-BB03-EBA3242EB48B"; + DownloadItem* persisted_item = download_manager_->CreateDownloadItem( + kGuid, + 10, + base::FilePath(), + base::FilePath(), + std::vector<GURL>(), + GURL("http://example.com/a"), + "application/octet-stream", + "application/octet-stream", + base::Time::Now(), + base::Time::Now(), + std::string(), + std::string(), + 10, + 10, + std::string(), + DownloadItem::INTERRUPTED, + DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, + DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED, + false); + ASSERT_TRUE(persisted_item); + + ASSERT_EQ(persisted_item, download_manager_->GetDownloadByGuid(kGuid)); +} + +namespace { + +base::Callback<bool(const GURL&)> GetSingleURLFilter(const GURL& url) { + return base::Bind(&GURL::operator==, base::Owned(new GURL(url))); +} + +} // namespace + +// Confirm that only downloads with the specified URL are removed. +TEST_F(DownloadManagerTest, RemoveDownloadsByURL) { base::Time now(base::Time::Now()); for (uint32_t i = 0; i < 2; ++i) { MockDownloadItemImpl& item(AddItemToManager()); @@ -720,9 +790,10 @@ TEST_F(DownloadManagerTest, RemoveSameOriginDownloads) { EXPECT_CALL(GetMockDownloadItem(0), Remove()); EXPECT_CALL(GetMockDownloadItem(1), Remove()).Times(0); - url::Origin origin_to_clear(download_urls_[0]); - int remove_count = download_manager_->RemoveDownloadsByOriginAndTime( - origin_to_clear, base::Time(), base::Time::Max()); + base::Callback<bool(const GURL&)> url_filter = + GetSingleURLFilter(download_urls_[0]); + int remove_count = download_manager_->RemoveDownloadsByURLAndTime( + url_filter, base::Time(), base::Time::Max()); EXPECT_EQ(remove_count, 1); } diff --git a/chromium/content/browser/download/download_net_log_parameters.cc b/chromium/content/browser/download/download_net_log_parameters.cc index e81c813af2f..110bc98c3b0 100644 --- a/chromium/content/browser/download/download_net_log_parameters.cc +++ b/chromium/content/browser/download/download_net_log_parameters.cc @@ -89,14 +89,11 @@ scoped_ptr<base::Value> ItemRenamedNetLogCallback( scoped_ptr<base::Value> ItemInterruptedNetLogCallback( DownloadInterruptReason reason, int64_t bytes_so_far, - const std::string* hash_state, net::NetLogCaptureMode capture_mode) { scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); dict->SetString("interrupt_reason", DownloadInterruptReasonToString(reason)); dict->SetString("bytes_so_far", base::Int64ToString(bytes_so_far)); - dict->SetString("hash_state", - base::HexEncode(hash_state->data(), hash_state->size())); return std::move(dict); } @@ -105,15 +102,12 @@ scoped_ptr<base::Value> ItemResumingNetLogCallback( bool user_initiated, DownloadInterruptReason reason, int64_t bytes_so_far, - const std::string* hash_state, net::NetLogCaptureMode capture_mode) { scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); dict->SetString("user_initiated", user_initiated ? "true" : "false"); dict->SetString("interrupt_reason", DownloadInterruptReasonToString(reason)); dict->SetString("bytes_so_far", base::Int64ToString(bytes_so_far)); - dict->SetString("hash_state", - base::HexEncode(hash_state->data(), hash_state->size())); return std::move(dict); } @@ -143,13 +137,10 @@ scoped_ptr<base::Value> ItemFinishedNetLogCallback( scoped_ptr<base::Value> ItemCanceledNetLogCallback( int64_t bytes_so_far, - const std::string* hash_state, net::NetLogCaptureMode capture_mode) { scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); dict->SetString("bytes_so_far", base::Int64ToString(bytes_so_far)); - dict->SetString("hash_state", - base::HexEncode(hash_state->data(), hash_state->size())); return std::move(dict); } diff --git a/chromium/content/browser/download/download_net_log_parameters.h b/chromium/content/browser/download/download_net_log_parameters.h index 2f4b6a006e5..b10a345ed33 100644 --- a/chromium/content/browser/download/download_net_log_parameters.h +++ b/chromium/content/browser/download/download_net_log_parameters.h @@ -50,7 +50,6 @@ scoped_ptr<base::Value> ItemRenamedNetLogCallback( scoped_ptr<base::Value> ItemInterruptedNetLogCallback( DownloadInterruptReason reason, int64_t bytes_so_far, - const std::string* hash_state, net::NetLogCaptureMode capture_mode); // Returns NetLog parameters when a DownloadItem is resumed. @@ -58,7 +57,6 @@ scoped_ptr<base::Value> ItemResumingNetLogCallback( bool user_initiated, DownloadInterruptReason reason, int64_t bytes_so_far, - const std::string* hash_state, net::NetLogCaptureMode capture_mode); // Returns NetLog parameters when a DownloadItem is completing. @@ -75,7 +73,6 @@ scoped_ptr<base::Value> ItemFinishedNetLogCallback( // Returns NetLog parameters when a DownloadItem is canceled. scoped_ptr<base::Value> ItemCanceledNetLogCallback( int64_t bytes_so_far, - const std::string* hash_state, net::NetLogCaptureMode capture_mode); // Returns NetLog parameters when a DownloadFile is opened. diff --git a/chromium/content/browser/download/download_request_core.cc b/chromium/content/browser/download/download_request_core.cc index 072ba7b0b55..3f70e956baa 100644 --- a/chromium/content/browser/download/download_request_core.cc +++ b/chromium/content/browser/download/download_request_core.cc @@ -8,6 +8,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" +#include "base/format_macros.h" #include "base/location.h" #include "base/logging.h" #include "base/metrics/histogram_macros.h" @@ -21,41 +22,197 @@ #include "content/browser/download/download_manager_impl.h" #include "content/browser/download/download_request_handle.h" #include "content/browser/download/download_stats.h" +#include "content/browser/loader/resource_dispatcher_host_impl.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_interrupt_reasons.h" #include "content/public/browser/download_item.h" #include "content/public/browser/download_manager_delegate.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/power_save_blocker.h" +#include "content/public/browser/resource_context.h" #include "content/public/browser/web_contents.h" +#include "net/base/elements_upload_data_stream.h" #include "net/base/io_buffer.h" +#include "net/base/load_flags.h" #include "net/base/net_errors.h" +#include "net/base/upload_bytes_element_reader.h" #include "net/http/http_response_headers.h" #include "net/http/http_status_code.h" #include "net/url_request/url_request_context.h" namespace content { +namespace { + +// This is a UserData::Data that will be attached to a URLRequest as a +// side-channel for passing download parameters. +class DownloadRequestData : public base::SupportsUserData::Data { + public: + ~DownloadRequestData() override {} + + static void Attach(net::URLRequest* request, + DownloadUrlParameters* download_parameters, + uint32_t download_id); + static DownloadRequestData* Get(net::URLRequest* request); + static void Detach(net::URLRequest* request); + + scoped_ptr<DownloadSaveInfo> TakeSaveInfo() { return std::move(save_info_); } + uint32_t download_id() const { return download_id_; } + const DownloadUrlParameters::OnStartedCallback& callback() const { + return on_started_callback_; + } + + private: + static const int kKey; + + scoped_ptr<DownloadSaveInfo> save_info_; + uint32_t download_id_ = DownloadItem::kInvalidId; + DownloadUrlParameters::OnStartedCallback on_started_callback_; +}; + +// static +const int DownloadRequestData::kKey = 0; + +// static +void DownloadRequestData::Attach(net::URLRequest* request, + DownloadUrlParameters* parameters, + uint32_t download_id) { + DownloadRequestData* request_data = new DownloadRequestData; + request_data->save_info_.reset( + new DownloadSaveInfo(parameters->GetSaveInfo())); + request_data->download_id_ = download_id; + request_data->on_started_callback_ = parameters->callback(); + request->SetUserData(&kKey, request_data); +} + +// static +DownloadRequestData* DownloadRequestData::Get(net::URLRequest* request) { + return static_cast<DownloadRequestData*>(request->GetUserData(&kKey)); +} + +// static +void DownloadRequestData::Detach(net::URLRequest* request) { + request->RemoveUserData(&kKey); +} + +} // namespace + const int DownloadRequestCore::kDownloadByteStreamSize = 100 * 1024; -DownloadRequestCore::DownloadRequestCore( - net::URLRequest* request, - scoped_ptr<DownloadSaveInfo> save_info, - const base::Closure& on_ready_to_read_callback) - : on_ready_to_read_callback_(on_ready_to_read_callback), +// static +scoped_ptr<net::URLRequest> DownloadRequestCore::CreateRequestOnIOThread( + uint32_t download_id, + DownloadUrlParameters* params) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK(download_id == DownloadItem::kInvalidId || + !params->content_initiated()) + << "Content initiated downloads shouldn't specify a download ID"; + + // ResourceDispatcherHost{Base} is-not-a URLRequest::Delegate, and + // DownloadUrlParameters can-not include resource_dispatcher_host_impl.h, so + // we must down cast. RDHI is the only subclass of RDH as of 2012 May 4. + scoped_ptr<net::URLRequest> request( + params->resource_context()->GetRequestContext()->CreateRequest( + params->url(), net::DEFAULT_PRIORITY, nullptr)); + request->set_method(params->method()); + + if (!params->post_body().empty()) { + const std::string& body = params->post_body(); + scoped_ptr<net::UploadElementReader> reader( + net::UploadOwnedBytesElementReader::CreateWithString(body)); + request->set_upload( + net::ElementsUploadDataStream::CreateWithReader(std::move(reader), 0)); + } + + if (params->post_id() >= 0) { + // The POST in this case does not have an actual body, and only works + // when retrieving data from cache. This is done because we don't want + // to do a re-POST without user consent, and currently don't have a good + // plan on how to display the UI for that. + DCHECK(params->prefer_cache()); + DCHECK_EQ("POST", params->method()); + std::vector<scoped_ptr<net::UploadElementReader>> element_readers; + request->set_upload(make_scoped_ptr(new net::ElementsUploadDataStream( + std::move(element_readers), params->post_id()))); + } + + int load_flags = request->load_flags(); + if (params->prefer_cache()) { + // If there is upload data attached, only retrieve from cache because there + // is no current mechanism to prompt the user for their consent for a + // re-post. For GETs, try to retrieve data from the cache and skip + // validating the entry if present. + if (request->get_upload()) + load_flags |= net::LOAD_ONLY_FROM_CACHE; + else + load_flags |= net::LOAD_PREFERRING_CACHE; + } else { + load_flags |= net::LOAD_DISABLE_CACHE; + } + request->SetLoadFlags(load_flags); + + bool has_last_modified = !params->last_modified().empty(); + bool has_etag = !params->etag().empty(); + + // If we've asked for a range, we want to make sure that we only get that + // range if our current copy of the information is good. We shouldn't be + // asked to continue if we don't have a verifier. + DCHECK(params->offset() == 0 || has_etag || has_last_modified); + + // If we're not at the beginning of the file, retrieve only the remaining + // portion. + if (params->offset() > 0 && (has_etag || has_last_modified)) { + request->SetExtraRequestHeaderByName( + "Range", base::StringPrintf("bytes=%" PRId64 "-", params->offset()), + true); + + // In accordance with RFC 2616 Section 14.27, use If-Range to specify that + // the server return the entire entity if the validator doesn't match. + // Last-Modified can be used in the absence of ETag as a validator if the + // response headers satisfied the HttpUtil::HasStrongValidators() predicate. + // + // This function assumes that HasStrongValidators() was true and that the + // ETag and Last-Modified header values supplied are valid. + request->SetExtraRequestHeaderByName( + "If-Range", has_etag ? params->etag() : params->last_modified(), true); + } + + for (const auto& header : params->request_headers()) + request->SetExtraRequestHeaderByName(header.first, header.second, + false /*overwrite*/); + + DownloadRequestData::Attach(request.get(), std::move(params), download_id); + return request; +} + +DownloadRequestCore::DownloadRequestCore(net::URLRequest* request, + Delegate* delegate) + : delegate_(delegate), request_(request), - save_info_(std::move(save_info)), + download_id_(DownloadItem::kInvalidId), last_buffer_size_(0), bytes_read_(0), pause_count_(0), - was_deferred_(false) { + was_deferred_(false), + is_partial_request_(false), + started_(false), + abort_reason_(DOWNLOAD_INTERRUPT_REASON_NONE) { DCHECK(request_); - DCHECK(save_info_); - DCHECK(!on_ready_to_read_callback_.is_null()); + DCHECK(delegate_); RecordDownloadCount(UNTHROTTLED_COUNT); power_save_blocker_ = PowerSaveBlocker::Create( PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, PowerSaveBlocker::kReasonOther, "Download in progress"); + DownloadRequestData* request_data = DownloadRequestData::Get(request_); + if (request_data) { + save_info_ = request_data->TakeSaveInfo(); + download_id_ = request_data->download_id(); + on_started_callback_ = request_data->callback(); + DownloadRequestData::Detach(request_); + is_partial_request_ = save_info_->offset > 0; + } else { + save_info_.reset(new DownloadSaveInfo); + } } DownloadRequestCore::~DownloadRequestCore() { @@ -68,15 +225,41 @@ DownloadRequestCore::~DownloadRequestCore() { base::TimeTicks::Now() - download_start_time_); } -// Send the download creation information to the download thread. -void DownloadRequestCore::OnResponseStarted( - scoped_ptr<DownloadCreateInfo>* create_info, - scoped_ptr<ByteStreamReader>* stream_reader) { +scoped_ptr<DownloadCreateInfo> DownloadRequestCore::CreateDownloadCreateInfo( + DownloadInterruptReason result) { + DCHECK(!started_); + started_ = true; + scoped_ptr<DownloadCreateInfo> create_info(new DownloadCreateInfo( + base::Time::Now(), request()->net_log(), std::move(save_info_))); + + if (result == DOWNLOAD_INTERRUPT_REASON_NONE) + create_info->remote_address = request()->GetSocketAddress().host(); + create_info->url_chain = request()->url_chain(); + create_info->referrer_url = GURL(request()->referrer()); + create_info->result = result; + create_info->download_id = download_id_; + return create_info; +} + +bool DownloadRequestCore::OnResponseStarted( + const std::string& override_mime_type) { DCHECK_CURRENTLY_ON(BrowserThread::IO); - DCHECK(save_info_); DVLOG(20) << __FUNCTION__ << "()" << DebugString(); download_start_time_ = base::TimeTicks::Now(); + DownloadInterruptReason result = + request()->response_headers() + ? HandleSuccessfulServerResponse(*request()->response_headers(), + save_info_.get()) + : DOWNLOAD_INTERRUPT_REASON_NONE; + + scoped_ptr<DownloadCreateInfo> create_info = CreateDownloadCreateInfo(result); + if (result != DOWNLOAD_INTERRUPT_REASON_NONE) { + delegate_->OnStart(std::move(create_info), scoped_ptr<ByteStreamReader>(), + base::ResetAndReturn(&on_started_callback_)); + return false; + } + // If it's a download, we don't want to poison the cache with it. request()->StopCaching(); @@ -90,35 +273,21 @@ void DownloadRequestCore::OnResponseStarted( int64_t content_length = request()->GetExpectedContentSize() > 0 ? request()->GetExpectedContentSize() : 0; - - // Deleted in DownloadManager. - scoped_ptr<DownloadCreateInfo> info( - new DownloadCreateInfo(base::Time::Now(), content_length, - request()->net_log(), std::move(save_info_))); + create_info->total_bytes = content_length; // Create the ByteStream for sending data to the download sink. + scoped_ptr<ByteStreamReader> stream_reader; CreateByteStream( base::ThreadTaskRunnerHandle::Get(), BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), - kDownloadByteStreamSize, &stream_writer_, stream_reader); + kDownloadByteStreamSize, &stream_writer_, &stream_reader); stream_writer_->RegisterCallback( base::Bind(&DownloadRequestCore::ResumeRequest, AsWeakPtr())); - info->url_chain = request()->url_chain(); - info->referrer_url = GURL(request()->referrer()); - string mime_type; - request()->GetMimeType(&mime_type); - info->mime_type = mime_type; - info->remote_address = request()->GetSocketAddress().host(); - if (request()->response_headers()) { - // Grab the first content-disposition header. There may be more than one, - // though as of this writing, the network stack ensures if there are, they - // are all duplicates. - request()->response_headers()->EnumerateHeader( - nullptr, "Content-Disposition", &info->content_disposition); - } - RecordDownloadMimeType(info->mime_type); - RecordDownloadContentDisposition(info->content_disposition); + if (!override_mime_type.empty()) + create_info->mime_type = override_mime_type; + else + request()->GetMimeType(&create_info->mime_type); // Get the last modified time and etag. const net::HttpResponseHeaders* headers = request()->response_headers(); @@ -127,33 +296,49 @@ void DownloadRequestCore::OnResponseStarted( // If we don't have strong validators as per RFC 2616 section 13.3.3, then // we neither store nor use them for range requests. if (!headers->EnumerateHeader(nullptr, "Last-Modified", - &info->last_modified)) - info->last_modified.clear(); - if (!headers->EnumerateHeader(nullptr, "ETag", &info->etag)) - info->etag.clear(); + &create_info->last_modified)) + create_info->last_modified.clear(); + if (!headers->EnumerateHeader(nullptr, "ETag", &create_info->etag)) + create_info->etag.clear(); } - int status = headers->response_code(); - if (2 == status / 100 && status != net::HTTP_PARTIAL_CONTENT) { - // Success & not range response; if we asked for a range, we didn't - // get it--reset the file pointers to reflect that. - info->save_info->offset = 0; - info->save_info->hash_state = ""; - } + // Grab the first content-disposition header. There may be more than one, + // though as of this writing, the network stack ensures if there are, they + // are all duplicates. + headers->EnumerateHeader(nullptr, "Content-Disposition", + &create_info->content_disposition); - if (!headers->GetMimeType(&info->original_mime_type)) - info->original_mime_type.clear(); + if (!headers->GetMimeType(&create_info->original_mime_type)) + create_info->original_mime_type.clear(); } // Blink verifies that the requester of this download is allowed to set a - // suggested name for the security origin of the downlaod URL. However, this + // suggested name for the security origin of the download URL. However, this // assumption doesn't hold if there were cross origin redirects. Therefore, // clear the suggested_name for such requests. - if (info->url_chain.size() > 1 && - info->url_chain.front().GetOrigin() != info->url_chain.back().GetOrigin()) - info->save_info->suggested_name.clear(); + if (create_info->url_chain.size() > 1 && + create_info->url_chain.front().GetOrigin() != + create_info->url_chain.back().GetOrigin()) + create_info->save_info->suggested_name.clear(); + + RecordDownloadMimeType(create_info->mime_type); + RecordDownloadContentDisposition(create_info->content_disposition); + + delegate_->OnStart(std::move(create_info), std::move(stream_reader), + base::ResetAndReturn(&on_started_callback_)); + return true; +} - info.swap(*create_info); +bool DownloadRequestCore::OnRequestRedirected() { + DVLOG(20) << __FUNCTION__ << "() " << DebugString(); + if (is_partial_request_) { + // A redirect while attempting a partial resumption indicates a potential + // middle box. Trigger another interruption so that the DownloadItem can + // retry. + abort_reason_ = DOWNLOAD_INTERRUPT_REASON_SERVER_UNREACHABLE; + return false; + } + return true; } // Create a new buffer, which will be handed to the download thread for file @@ -212,7 +397,13 @@ bool DownloadRequestCore::OnReadCompleted(int bytes_read, bool* defer) { return true; } -DownloadInterruptReason DownloadRequestCore::OnResponseCompleted( +void DownloadRequestCore::OnWillAbort(DownloadInterruptReason reason) { + DVLOG(20) << __FUNCTION__ << "() reason=" << reason << " " << DebugString(); + DCHECK(!started_); + abort_reason_ = reason; +} + +void DownloadRequestCore::OnResponseCompleted( const net::URLRequestStatus& status) { DCHECK_CURRENTLY_ON(BrowserThread::IO); int response_code = status.is_success() ? request()->GetResponseCode() : 0; @@ -221,80 +412,27 @@ DownloadInterruptReason DownloadRequestCore::OnResponseCompleted( << " status.error() = " << status.error() << " response_code = " << response_code; - net::Error error_code = net::OK; - if (status.status() == net::URLRequestStatus::FAILED || - // Note cancels as failures too. - status.status() == net::URLRequestStatus::CANCELED) { - error_code = static_cast<net::Error>(status.error()); // Normal case. - // Make sure that at least the fact of failure comes through. - if (error_code == net::OK) - error_code = net::ERR_FAILED; - } - - // ERR_CONTENT_LENGTH_MISMATCH and ERR_INCOMPLETE_CHUNKED_ENCODING are - // allowed since a number of servers in the wild close the connection too - // early by mistake. Other browsers - IE9, Firefox 11.0, and Safari 5.1.4 - - // treat downloads as complete in both cases, so we follow their lead. - if (error_code == net::ERR_CONTENT_LENGTH_MISMATCH || - error_code == net::ERR_INCOMPLETE_CHUNKED_ENCODING) { - error_code = net::OK; - } - DownloadInterruptReason reason = ConvertNetErrorToInterruptReason( - error_code, DOWNLOAD_INTERRUPT_FROM_NETWORK); - - if (status.status() == net::URLRequestStatus::CANCELED && - status.error() == net::ERR_ABORTED) { - // CANCELED + ERR_ABORTED == something outside of the network - // stack cancelled the request. There aren't that many things that - // could do this to a download request (whose lifetime is separated from - // the tab from which it came). We map this to USER_CANCELLED as the - // case we know about (system suspend because of laptop close) corresponds - // to a user action. - // TODO(ahendrickson) -- Find a better set of codes to use here, as - // CANCELED/ERR_ABORTED can occur for reasons other than user cancel. - if (net::IsCertStatusError(request()->ssl_info().cert_status)) - reason = DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM; - else - reason = DOWNLOAD_INTERRUPT_REASON_USER_CANCELED; - } - - if (status.is_success() && reason == DOWNLOAD_INTERRUPT_REASON_NONE && - request()->response_headers()) { - // Handle server's response codes. - switch (response_code) { - case -1: // Non-HTTP request. - case net::HTTP_OK: - case net::HTTP_CREATED: - case net::HTTP_ACCEPTED: - case net::HTTP_NON_AUTHORITATIVE_INFORMATION: - case net::HTTP_RESET_CONTENT: - case net::HTTP_PARTIAL_CONTENT: - // Expected successful codes. - break; - case net::HTTP_NO_CONTENT: - case net::HTTP_NOT_FOUND: - reason = DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT; - break; - case net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: - // Retry by downloading from the start automatically: - // If we haven't received data when we get this error, we won't. - reason = DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE; - break; - case net::HTTP_UNAUTHORIZED: - // Server didn't authorize this request. - reason = DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED; - break; - case net::HTTP_FORBIDDEN: - // Server forbids access to this resource. - reason = DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN; - break; - default: // All other errors. - // Redirection and informational codes should have been handled earlier - // in the stack. - DCHECK_NE(3, response_code / 100); - DCHECK_NE(1, response_code / 100); - reason = DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED; - break; + DownloadInterruptReason reason = HandleRequestStatus(status); + + if (status.status() == net::URLRequestStatus::CANCELED) { + if (abort_reason_ != DOWNLOAD_INTERRUPT_REASON_NONE) { + // If a more specific interrupt reason was specified before the request + // was explicitly cancelled, then use it. + reason = abort_reason_; + } else if (status.error() == net::ERR_ABORTED) { + // CANCELED + ERR_ABORTED == something outside of the network + // stack cancelled the request. There aren't that many things that + // could do this to a download request (whose lifetime is separated from + // the tab from which it came). We map this to USER_CANCELLED as the + // case we know about (system suspend because of laptop close) corresponds + // to a user action. + // TODO(asanka): A lid close or other power event should result in an + // interruption that doesn't discard the partial state, unlike + // USER_CANCELLED. (https://crbug.com/166179) + if (net::IsCertStatusError(request()->ssl_info().cert_status)) + reason = DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM; + else + reason = DOWNLOAD_INTERRUPT_REASON_USER_CANCELED; } } @@ -325,7 +463,16 @@ DownloadInterruptReason DownloadRequestCore::OnResponseCompleted( stream_writer_.reset(); // We no longer need the stream. read_buffer_ = nullptr; - return reason; + if (started_) + return; + + // OnResponseCompleted() called without OnResponseStarted(). This should only + // happen when the request was aborted. + DCHECK_NE(reason, DOWNLOAD_INTERRUPT_REASON_NONE); + scoped_ptr<DownloadCreateInfo> create_info = CreateDownloadCreateInfo(reason); + scoped_ptr<ByteStreamReader> empty_byte_stream; + delegate_->OnStart(std::move(create_info), std::move(empty_byte_stream), + base::ResetAndReturn(&on_started_callback_)); } void DownloadRequestCore::PauseRequest() { @@ -351,16 +498,136 @@ void DownloadRequestCore::ResumeRequest() { last_stream_pause_time_ = base::TimeTicks(); } - on_ready_to_read_callback_.Run(); + delegate_->OnReadyToRead(); } std::string DownloadRequestCore::DebugString() const { return base::StringPrintf( "{" + " this=%p " " url_ = " "\"%s\"" " }", + reinterpret_cast<const void*>(this), request() ? request()->url().spec().c_str() : "<NULL request>"); } +// static +DownloadInterruptReason DownloadRequestCore::HandleRequestStatus( + const net::URLRequestStatus& status) { + net::Error error_code = net::OK; + if (status.status() == net::URLRequestStatus::FAILED || + // Note cancels as failures too. + status.status() == net::URLRequestStatus::CANCELED) { + error_code = static_cast<net::Error>(status.error()); // Normal case. + // Make sure that at least the fact of failure comes through. + if (error_code == net::OK) + error_code = net::ERR_FAILED; + } + + // ERR_CONTENT_LENGTH_MISMATCH and ERR_INCOMPLETE_CHUNKED_ENCODING are + // allowed since a number of servers in the wild close the connection too + // early by mistake. Other browsers - IE9, Firefox 11.0, and Safari 5.1.4 - + // treat downloads as complete in both cases, so we follow their lead. + if (error_code == net::ERR_CONTENT_LENGTH_MISMATCH || + error_code == net::ERR_INCOMPLETE_CHUNKED_ENCODING) { + error_code = net::OK; + } + DownloadInterruptReason reason = ConvertNetErrorToInterruptReason( + error_code, DOWNLOAD_INTERRUPT_FROM_NETWORK); + + return reason; +} + +// static +DownloadInterruptReason DownloadRequestCore::HandleSuccessfulServerResponse( + const net::HttpResponseHeaders& http_headers, + DownloadSaveInfo* save_info) { + switch (http_headers.response_code()) { + case -1: // Non-HTTP request. + case net::HTTP_OK: + case net::HTTP_NON_AUTHORITATIVE_INFORMATION: + case net::HTTP_PARTIAL_CONTENT: + // Expected successful codes. + break; + + case net::HTTP_CREATED: + case net::HTTP_ACCEPTED: + // Per RFC 2616 the entity being transferred is metadata about the + // resource at the target URL and not the resource at that URL (or the + // resource that would be at the URL once processing is completed in the + // case of HTTP_ACCEPTED). However, we currently don't have special + // handling for these response and they are downloaded the same as a + // regular response. + break; + + case net::HTTP_NO_CONTENT: + case net::HTTP_RESET_CONTENT: + // These two status codes don't have an entity (or rather RFC 2616 + // requires that there be no entity). They are treated the same as the + // resource not being found since there is no entity to download. + + case net::HTTP_NOT_FOUND: + return DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT; + break; + + case net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: + // Retry by downloading from the start automatically: + // If we haven't received data when we get this error, we won't. + return DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE; + break; + case net::HTTP_UNAUTHORIZED: + case net::HTTP_PROXY_AUTHENTICATION_REQUIRED: + // Server didn't authorize this request. + return DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED; + break; + case net::HTTP_FORBIDDEN: + // Server forbids access to this resource. + return DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN; + break; + default: // All other errors. + // Redirection and informational codes should have been handled earlier + // in the stack. + DCHECK_NE(3, http_headers.response_code() / 100); + DCHECK_NE(1, http_headers.response_code() / 100); + return DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED; + } + + if (save_info && save_info->offset > 0) { + // The caller is expecting a partial response. + + if (http_headers.response_code() != net::HTTP_PARTIAL_CONTENT) { + // Requested a partial range, but received the entire response. + save_info->offset = 0; + save_info->hash_of_partial_file.clear(); + save_info->hash_state.reset(); + return DOWNLOAD_INTERRUPT_REASON_NONE; + } + + int64_t first_byte = -1; + int64_t last_byte = -1; + int64_t length = -1; + if (!http_headers.GetContentRange(&first_byte, &last_byte, &length)) + return DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT; + DCHECK_GE(first_byte, 0); + + if (first_byte != save_info->offset) { + // The server returned a different range than the one we requested. Assume + // the response is bad. + // + // In the future we should consider allowing offsets that are less than + // the offset we've requested, since in theory we can truncate the partial + // file at the offset and continue. + return DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT; + } + + return DOWNLOAD_INTERRUPT_REASON_NONE; + } + + if (http_headers.response_code() == net::HTTP_PARTIAL_CONTENT) + return DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT; + + return DOWNLOAD_INTERRUPT_REASON_NONE; +} + } // namespace content diff --git a/chromium/content/browser/download/download_request_core.h b/chromium/content/browser/download/download_request_core.h index 22ca862ef85..ac9e9b6cd86 100644 --- a/chromium/content/browser/download/download_request_core.h +++ b/chromium/content/browser/download/download_request_core.h @@ -20,7 +20,9 @@ #include "content/public/browser/download_url_parameters.h" namespace net { +class HttpResponseHeaders; class URLRequest; +class URLRequestStatus; } // namespace net namespace content { @@ -38,29 +40,29 @@ struct DownloadCreateInfo; class CONTENT_EXPORT DownloadRequestCore : public base::SupportsWeakPtr<DownloadRequestCore> { public: - // Size of the buffer used between the DownloadRequestCore and the - // downstream receiver of its output. - static const int kDownloadByteStreamSize; - - // |request| *must* outlive the DownloadRequestCore. |save_info| must be - // valid. - // - // Invokes |on_ready_to_read_callback| if a previous call to OnReadCompleted() - // resulted in |defer| being set to true, and DownloadRequestCore is now ready - // to commence reading. - DownloadRequestCore(net::URLRequest* request, - scoped_ptr<DownloadSaveInfo> save_info, - const base::Closure& on_ready_to_read_callback); + class Delegate { + public: + virtual void OnReadyToRead() = 0; + virtual void OnStart( + scoped_ptr<DownloadCreateInfo> download_create_info, + scoped_ptr<ByteStreamReader> stream_reader, + const DownloadUrlParameters::OnStartedCallback& callback) = 0; + }; + + // All parameters are required. |request| and |delegate| must outlive + // DownloadRequestCore. + DownloadRequestCore(net::URLRequest* request, Delegate* delegate); ~DownloadRequestCore(); // Should be called when the URLRequest::Delegate receives OnResponseStarted. - // Constructs a DownloadCreateInfo and a ByteStreamReader that should be - // passed into DownloadManagerImpl::StartDownload(). - // - // Only populates the response derived fields of DownloadCreateInfo, with the - // exception of |save_info|. - void OnResponseStarted(scoped_ptr<DownloadCreateInfo>* info, - scoped_ptr<ByteStreamReader>* stream_reader); + // Invokes Delegate::OnStart() with download start parameters. The + // |override_mime_type| is used as the MIME type for the download when + // constructing a DownloadCreateInfo object. + bool OnResponseStarted(const std::string& override_mime_type); + + // Should be called to handle a redirect. The caller should only allow the + // redirect to be followed if the return value is true. + bool OnRequestRedirected(); // Starts a read cycle. Creates a new IOBuffer which can be passed into // URLRequest::Read(). Call OnReadCompleted() when the Read operation @@ -69,20 +71,22 @@ class CONTENT_EXPORT DownloadRequestCore int* buf_size, int min_size); + // Used to notify DownloadRequestCore that the caller is about to abort the + // outer request. |reason| will be used as the final interrupt reason when + // OnResponseCompleted() is called. + void OnWillAbort(DownloadInterruptReason reason); + // Should be called when the Read() operation completes. |defer| will be set // to true if reading is to be suspended. In the latter case, once more data // can be read, invokes the |on_ready_to_read_callback|. bool OnReadCompleted(int bytes_read, bool* defer); - // Called to signal that the response is complete. If the return value is - // something other than DOWNLOAD_INTERRUPT_REASON_NONE, then the download - // should be considered interrupted. + // Called to signal that the response is complete. // // It is expected that once this method is invoked, the DownloadRequestCore // object will be destroyed in short order without invoking any other methods // other than the destructor. - DownloadInterruptReason OnResponseCompleted( - const net::URLRequestStatus& status); + void OnResponseCompleted(const net::URLRequestStatus& status); // Called if the request should suspend reading. A subsequent // OnReadCompleted() will result in |defer| being set to true. @@ -98,13 +102,36 @@ class CONTENT_EXPORT DownloadRequestCore std::string DebugString() const; + static scoped_ptr<net::URLRequest> CreateRequestOnIOThread( + uint32_t download_id, + DownloadUrlParameters* params); + + // Size of the buffer used between the DownloadRequestCore and the + // downstream receiver of its output. + static const int kDownloadByteStreamSize; + protected: net::URLRequest* request() const { return request_; } private: - base::Closure on_ready_to_read_callback_; + static DownloadInterruptReason HandleRequestStatus( + const net::URLRequestStatus& status); + + static DownloadInterruptReason HandleSuccessfulServerResponse( + const net::HttpResponseHeaders& http_headers, + DownloadSaveInfo* save_info); + + scoped_ptr<DownloadCreateInfo> CreateDownloadCreateInfo( + DownloadInterruptReason result); + + Delegate* delegate_; net::URLRequest* request_; + + // "Passthrough" fields. These are only kept here so that they can be used to + // populate the DownloadCreateInfo when the time comes. scoped_ptr<DownloadSaveInfo> save_info_; + uint32_t download_id_; + DownloadUrlParameters::OnStartedCallback on_started_callback_; // Data flow scoped_refptr<net::IOBuffer> read_buffer_; // From URLRequest. @@ -125,6 +152,15 @@ class CONTENT_EXPORT DownloadRequestCore int pause_count_; bool was_deferred_; + bool is_partial_request_; + bool started_; + + // When DownloadRequestCore initiates an abort (by blocking a redirect, for + // example) it expects to eventually receive a OnResponseCompleted() with a + // status indicating that the request was aborted. When this happens, the + // interrupt reason in |abort_reason_| will be used instead of USER_CANCELED + // which is vague. + DownloadInterruptReason abort_reason_; // Each successful OnWillRead will yield a buffer of this size. static const int kReadBufSize = 32768; // bytes diff --git a/chromium/content/browser/download/download_request_handle.cc b/chromium/content/browser/download/download_request_handle.cc index 67dda4f5029..f5979b945ad 100644 --- a/chromium/content/browser/download/download_request_handle.cc +++ b/chromium/content/browser/download/download_request_handle.cc @@ -6,88 +6,39 @@ #include "base/bind.h" #include "base/strings/stringprintf.h" -#include "content/browser/frame_host/navigator.h" -#include "content/browser/frame_host/render_frame_host_impl.h" -#include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" -#include "content/public/common/browser_side_navigation_policy.h" namespace content { DownloadRequestHandleInterface::~DownloadRequestHandleInterface() {} +DownloadRequestHandle::DownloadRequestHandle( + const DownloadRequestHandle& other) = default; + DownloadRequestHandle::~DownloadRequestHandle() {} -DownloadRequestHandle::DownloadRequestHandle() - : child_id_(-1), - render_view_id_(-1), - request_id_(-1), - frame_tree_node_id_(-1) { -} +DownloadRequestHandle::DownloadRequestHandle() {} DownloadRequestHandle::DownloadRequestHandle( const base::WeakPtr<DownloadResourceHandler>& handler, - int child_id, - int render_view_id, - int request_id, - int frame_tree_node_id) - : handler_(handler), - child_id_(child_id), - render_view_id_(render_view_id), - request_id_(request_id), - frame_tree_node_id_(frame_tree_node_id) { + const content::ResourceRequestInfo::WebContentsGetter& web_contents_getter) + : handler_(handler), web_contents_getter_(web_contents_getter) { DCHECK(handler_.get()); } WebContents* DownloadRequestHandle::GetWebContents() const { - // PlzNavigate: if a FrameTreeNodeId was provided, use it to return the - // WebContents. - // TODO(davidben): This logic should be abstracted within the ResourceLoader - // stack. https://crbug.com/376003 - if (IsBrowserSideNavigationEnabled()) { - FrameTreeNode* frame_tree_node = - FrameTreeNode::GloballyFindByID(frame_tree_node_id_); - if (frame_tree_node) { - return WebContentsImpl::FromFrameTreeNode(frame_tree_node); - } - } - - RenderViewHostImpl* render_view_host = - RenderViewHostImpl::FromID(child_id_, render_view_id_); - if (!render_view_host) - return nullptr; - - return render_view_host->GetDelegate()->GetAsWebContents(); + return web_contents_getter_.is_null() ? nullptr : web_contents_getter_.Run(); } DownloadManager* DownloadRequestHandle::GetDownloadManager() const { - // PlzNavigate: if a FrameTreeNodeId was provided, use it to return the - // DownloadManager. - // TODO(davidben): This logic should be abstracted within the ResourceLoader - // stack. https://crbug.com/376003 - if (IsBrowserSideNavigationEnabled()) { - FrameTreeNode* frame_tree_node = - FrameTreeNode::GloballyFindByID(frame_tree_node_id_); - if (frame_tree_node) { - BrowserContext* context = - frame_tree_node->navigator()->GetController()->GetBrowserContext(); - if (context) - return BrowserContext::GetDownloadManager(context); - } - } - - RenderViewHostImpl* rvh = RenderViewHostImpl::FromID( - child_id_, render_view_id_); - if (rvh == NULL) - return NULL; - RenderProcessHost* rph = rvh->GetProcess(); - if (rph == NULL) - return NULL; - BrowserContext* context = rph->GetBrowserContext(); - if (context == NULL) - return NULL; + WebContents* web_contents = GetWebContents(); + if (web_contents == nullptr) + return nullptr; + BrowserContext* context = web_contents->GetBrowserContext(); + if (context == nullptr) + return nullptr; return BrowserContext::GetDownloadManager(context); } @@ -109,15 +60,4 @@ void DownloadRequestHandle::CancelRequest() const { base::Bind(&DownloadResourceHandler::CancelRequest, handler_)); } -std::string DownloadRequestHandle::DebugString() const { - return base::StringPrintf("{" - " child_id = %d" - " render_view_id = %d" - " request_id = %d" - "}", - child_id_, - render_view_id_, - request_id_); -} - } // namespace content diff --git a/chromium/content/browser/download/download_request_handle.h b/chromium/content/browser/download/download_request_handle.h index 5d047c75910..090a25bb5a6 100644 --- a/chromium/content/browser/download/download_request_handle.h +++ b/chromium/content/browser/download/download_request_handle.h @@ -5,12 +5,11 @@ #ifndef CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_HANDLE_H_ #define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_REQUEST_HANDLE_H_ -#include <string> - #include "base/compiler_specific.h" #include "base/memory/weak_ptr.h" #include "content/browser/download/download_resource_handler.h" #include "content/common/content_export.h" +#include "content/public/browser/resource_request_info.h" namespace content { class DownloadManager; @@ -36,15 +35,13 @@ class CONTENT_EXPORT DownloadRequestHandleInterface { // Cancels the request. virtual void CancelRequest() const = 0; - - // Describes the object. - virtual std::string DebugString() const = 0; }; class CONTENT_EXPORT DownloadRequestHandle : public DownloadRequestHandleInterface { public: + DownloadRequestHandle(const DownloadRequestHandle& other); ~DownloadRequestHandle() override; // Create a null DownloadRequestHandle: getters will return null, and @@ -56,12 +53,9 @@ class CONTENT_EXPORT DownloadRequestHandle // allowing mocking of ResourceDispatcherHost in unit tests. DownloadRequestHandle(); - // Note that |rdh| is required to be non-null. DownloadRequestHandle(const base::WeakPtr<DownloadResourceHandler>& handler, - int child_id, - int render_view_id, - int request_id, - int frame_tree_node_id); + const content::ResourceRequestInfo::WebContentsGetter& + web_contents_getter); // Implement DownloadRequestHandleInterface interface. WebContents* GetWebContents() const override; @@ -69,23 +63,10 @@ class CONTENT_EXPORT DownloadRequestHandle void PauseRequest() const override; void ResumeRequest() const override; void CancelRequest() const override; - std::string DebugString() const override; private: base::WeakPtr<DownloadResourceHandler> handler_; - - // The ID of the child process that started the download. - int child_id_; - - // The ID of the render view that started the download. - int render_view_id_; - - // The ID associated with the request used for the download. - int request_id_; - - // PlzNavigate - // The ID of the FrameTreeNode that started the download. - int frame_tree_node_id_; + content::ResourceRequestInfo::WebContentsGetter web_contents_getter_; }; } // namespace content diff --git a/chromium/content/browser/download/download_resource_handler.cc b/chromium/content/browser/download/download_resource_handler.cc index e06b3095e99..30a7c8cf029 100644 --- a/chromium/content/browser/download/download_resource_handler.cc +++ b/chromium/content/browser/download/download_resource_handler.cc @@ -48,11 +48,11 @@ static void StartOnUIThread( // NULL in unittests or if the page closed right after starting the // download. if (!started_cb.is_null()) - started_cb.Run(NULL, DOWNLOAD_INTERRUPT_REASON_USER_CANCELED); - - // |stream| gets deleted on non-FILE thread, but it's ok since - // we're not using stream_writer_ yet. + started_cb.Run(nullptr, DOWNLOAD_INTERRUPT_REASON_USER_CANCELED); + if (stream) + BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE, + stream.release()); return; } @@ -83,19 +83,10 @@ void DeleteOnUIThread( } // namespace -DownloadResourceHandler::DownloadResourceHandler( - uint32_t id, - net::URLRequest* request, - const DownloadUrlParameters::OnStartedCallback& started_cb, - scoped_ptr<DownloadSaveInfo> save_info) +DownloadResourceHandler::DownloadResourceHandler(net::URLRequest* request) : ResourceHandler(request), - download_id_(id), - started_cb_(started_cb), tab_info_(new DownloadTabInfo()), - core_(request, - std::move(save_info), - base::Bind(&DownloadResourceHandler::OnCoreReadyToRead, - base::Unretained(this))) { + core_(request, this) { // Do UI thread initialization for tab_info_ asap after // DownloadResourceHandler creation since the tab could be navigated // before StartOnUIThread gets called. This is safe because deletion @@ -104,20 +95,14 @@ DownloadResourceHandler::DownloadResourceHandler( const ResourceRequestInfoImpl* request_info = GetRequestInfo(); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - base::Bind(&InitializeDownloadTabInfoOnUIThread, - DownloadRequestHandle(AsWeakPtr(), request_info->GetChildID(), - request_info->GetRouteID(), - request_info->GetRequestID(), - request_info->frame_tree_node_id()), - tab_info_.get())); + base::Bind( + &InitializeDownloadTabInfoOnUIThread, + DownloadRequestHandle(AsWeakPtr(), + request_info->GetWebContentsGetterForRequest()), + tab_info_.get())); } DownloadResourceHandler::~DownloadResourceHandler() { - // This won't do anything if the callback was called before. - // If it goes through, it will likely be because OnWillStart() returned - // false somewhere in the chain of resource handlers. - CallStartedCB(DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED); - if (tab_info_) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -129,46 +114,16 @@ bool DownloadResourceHandler::OnRequestRedirected( const net::RedirectInfo& redirect_info, ResourceResponse* response, bool* defer) { - return true; + return core_.OnRequestRedirected(); } // Send the download creation information to the download thread. bool DownloadResourceHandler::OnResponseStarted( ResourceResponse* response, bool* defer) { - scoped_ptr<DownloadCreateInfo> create_info; - scoped_ptr<ByteStreamReader> stream_reader; - - core_.OnResponseStarted(&create_info, &stream_reader); - - const ResourceRequestInfoImpl* request_info = GetRequestInfo(); - create_info->download_id = download_id_; - create_info->has_user_gesture = request_info->HasUserGesture(); - create_info->transition_type = request_info->GetPageTransition(); - create_info->request_handle.reset(new DownloadRequestHandle( - AsWeakPtr(), request_info->GetChildID(), request_info->GetRouteID(), - request_info->GetRequestID(), request_info->frame_tree_node_id())); - // The MIME type in ResourceResponse is the product of // MimeTypeResourceHandler. - create_info->mime_type = response->head.mime_type; - - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&StartOnUIThread, base::Passed(&create_info), - base::Passed(&tab_info_), base::Passed(&stream_reader), - base::ResetAndReturn(&started_cb_))); - return true; -} - -void DownloadResourceHandler::CallStartedCB( - DownloadInterruptReason interrupt_reason) { - DCHECK_CURRENTLY_ON(BrowserThread::IO); - if (started_cb_.is_null()) - return; - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(base::ResetAndReturn(&started_cb_), - nullptr, interrupt_reason)); + return core_.OnResponseStarted(response->head.mime_type); } bool DownloadResourceHandler::OnWillStart(const GURL& url, bool* defer) { @@ -197,8 +152,7 @@ void DownloadResourceHandler::OnResponseCompleted( const net::URLRequestStatus& status, const std::string& security_info, bool* defer) { - DownloadInterruptReason result = core_.OnResponseCompleted(status); - CallStartedCB(result); + core_.OnResponseCompleted(status); } void DownloadResourceHandler::OnDataDownloaded(int bytes_downloaded) { @@ -213,7 +167,36 @@ void DownloadResourceHandler::ResumeRequest() { core_.ResumeRequest(); } -void DownloadResourceHandler::OnCoreReadyToRead() { +void DownloadResourceHandler::OnStart( + scoped_ptr<DownloadCreateInfo> create_info, + scoped_ptr<ByteStreamReader> stream_reader, + const DownloadUrlParameters::OnStartedCallback& callback) { + // If the user cancels the download, then don't call start. Instead ignore the + // download entirely. + if (create_info->result == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED && + create_info->download_id == DownloadItem::kInvalidId) { + if (!callback.is_null()) + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(callback, nullptr, create_info->result)); + return; + } + + const ResourceRequestInfoImpl* request_info = GetRequestInfo(); + create_info->has_user_gesture = request_info->HasUserGesture(); + create_info->transition_type = request_info->GetPageTransition(); + + create_info->request_handle.reset(new DownloadRequestHandle( + AsWeakPtr(), request_info->GetWebContentsGetterForRequest())); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&StartOnUIThread, base::Passed(&create_info), + base::Passed(&tab_info_), base::Passed(&stream_reader), + callback)); +} + +void DownloadResourceHandler::OnReadyToRead() { DCHECK_CURRENTLY_ON(BrowserThread::IO); controller()->Resume(); } diff --git a/chromium/content/browser/download/download_resource_handler.h b/chromium/content/browser/download/download_resource_handler.h index 22a10a8a049..b874b9a43bb 100644 --- a/chromium/content/browser/download/download_resource_handler.h +++ b/chromium/content/browser/download/download_resource_handler.h @@ -33,17 +33,14 @@ struct DownloadCreateInfo; // Forwards data to the download thread. class CONTENT_EXPORT DownloadResourceHandler : public ResourceHandler, + public DownloadRequestCore::Delegate, public base::SupportsWeakPtr<DownloadResourceHandler> { public: struct DownloadTabInfo; // started_cb will be called exactly once on the UI thread. // |id| should be invalid if the id should be automatically assigned. - DownloadResourceHandler( - uint32_t id, - net::URLRequest* request, - const DownloadUrlParameters::OnStartedCallback& started_cb, - scoped_ptr<DownloadSaveInfo> save_info); + DownloadResourceHandler(net::URLRequest* request); bool OnRequestRedirected(const net::RedirectInfo& redirect_info, ResourceResponse* response, @@ -84,18 +81,12 @@ class CONTENT_EXPORT DownloadResourceHandler private: ~DownloadResourceHandler() override; - // Arrange for started_cb_ to be called on the UI thread with the - // below values, nulling out started_cb_. Should only be called - // on the IO thread. - void CallStartedCB(DownloadInterruptReason interrupt_reason); - - void OnCoreReadyToRead(); - - uint32_t download_id_; - - // This is read only on the IO thread, but may only - // be called on the UI thread. - DownloadUrlParameters::OnStartedCallback started_cb_; + // DownloadRequestCore::Delegate + void OnStart( + scoped_ptr<DownloadCreateInfo> download_create_info, + scoped_ptr<ByteStreamReader> stream_reader, + const DownloadUrlParameters::OnStartedCallback& callback) override; + void OnReadyToRead() override; // Stores information about the download that must be acquired on the UI // thread before StartOnUIThread is called. diff --git a/chromium/content/browser/download/drag_download_file.cc b/chromium/content/browser/download/drag_download_file.cc index afb533a56bf..dc6b89c8b8f 100644 --- a/chromium/content/browser/download/drag_download_file.cc +++ b/chromium/content/browser/download/drag_download_file.cc @@ -97,8 +97,8 @@ class DragDownloadFile::DragDownloadFileUI : public DownloadItem::Observer { void OnDownloadStarted(DownloadItem* item, DownloadInterruptReason interrupt_reason) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - if (!item) { - DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); + if (!item || item->GetState() != DownloadItem::IN_PROGRESS) { + DCHECK(!item || item->GetLastReason() != DOWNLOAD_INTERRUPT_REASON_NONE); on_completed_loop_->task_runner()->PostTask( FROM_HERE, base::Bind(on_completed_, false)); return; diff --git a/chromium/content/browser/download/mhtml_generation_manager.cc b/chromium/content/browser/download/mhtml_generation_manager.cc index ef9202330d2..623ab53546e 100644 --- a/chromium/content/browser/download/mhtml_generation_manager.cc +++ b/chromium/content/browser/download/mhtml_generation_manager.cc @@ -188,9 +188,8 @@ bool MHTMLGenerationManager::Job::SendToNextRenderFrame() { ipc_params.salt = salt_; ipc_params.digests_of_uris_to_skip = digests_of_already_serialized_uris_; - ipc_params.destination_file = IPC::GetFileHandleForProcess( - browser_file_.GetPlatformFile(), rfh->GetProcess()->GetHandle(), - false); // |close_source_handle|. + ipc_params.destination_file = IPC::GetPlatformFileForTransit( + browser_file_.GetPlatformFile(), false); // |close_source_handle|. ipc_params.frame_routing_id_to_content_id = CreateFrameRoutingIdToContentId(rfh->GetSiteInstance()); diff --git a/chromium/content/browser/download/mock_download_file.h b/chromium/content/browser/download/mock_download_file.h index a0020b686c9..7109c44c157 100644 --- a/chromium/content/browser/download/mock_download_file.h +++ b/chromium/content/browser/download/mock_download_file.h @@ -35,23 +35,24 @@ class MockDownloadFile : public DownloadFile { MOCK_METHOD2(RenameAndUniquify, void(const base::FilePath& full_path, const RenameCompletionCallback& callback)); - MOCK_METHOD2(RenameAndAnnotate, + MOCK_METHOD5(RenameAndAnnotate, void(const base::FilePath& full_path, + const std::string& client_guid, + const GURL& source_url, + const GURL& referrer_url, const RenameCompletionCallback& callback)); MOCK_METHOD0(Detach, void()); MOCK_METHOD0(Cancel, void()); MOCK_METHOD0(Finish, void()); - MOCK_CONST_METHOD0(FullPath, base::FilePath()); + MOCK_CONST_METHOD0(FullPath, const base::FilePath&()); MOCK_CONST_METHOD0(InProgress, bool()); MOCK_CONST_METHOD0(BytesSoFar, int64_t()); MOCK_CONST_METHOD0(CurrentSpeed, int64_t()); MOCK_METHOD1(GetHash, bool(std::string* hash)); - MOCK_METHOD0(GetHashState, std::string()); MOCK_METHOD0(SendUpdate, void()); MOCK_CONST_METHOD0(Id, int()); MOCK_METHOD0(GetDownloadManager, DownloadManager*()); MOCK_CONST_METHOD0(DebugString, std::string()); - MOCK_METHOD1(SetClientGuid, void(const std::string&)); }; } // namespace content diff --git a/chromium/content/browser/download/save_file.cc b/chromium/content/browser/download/save_file.cc index 5085e477c15..2eab52b0451 100644 --- a/chromium/content/browser/download/save_file.cc +++ b/chromium/content/browser/download/save_file.cc @@ -14,15 +14,7 @@ namespace content { // Unfortunately, as it is, constructors of SaveFile don't always // have access to the SavePackage at this point. SaveFile::SaveFile(const SaveFileCreateInfo* info, bool calculate_hash) - : file_(base::FilePath(), - info->url, - GURL(), - 0, - calculate_hash, - std::string(), - base::File(), - net::BoundNetLog()), - info_(info) { + : file_(net::BoundNetLog()), info_(info) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); DCHECK(info); @@ -34,7 +26,12 @@ SaveFile::~SaveFile() { } DownloadInterruptReason SaveFile::Initialize() { - return file_.Initialize(base::FilePath()); + return file_.Initialize(base::FilePath(), + base::FilePath(), + base::File(), + 0, + std::string(), + scoped_ptr<crypto::SecureHash>()); } DownloadInterruptReason SaveFile::AppendDataToFile(const char* data, @@ -61,7 +58,7 @@ void SaveFile::Finish() { void SaveFile::AnnotateWithSourceInformation() { // TODO(gbillock): If this method is called, it should set the // file_.SetClientGuid() method first. - file_.AnnotateWithSourceInformation(); + NOTREACHED(); } base::FilePath SaveFile::FullPath() const { @@ -76,10 +73,6 @@ int64_t SaveFile::BytesSoFar() const { return file_.bytes_so_far(); } -bool SaveFile::GetHash(std::string* hash) { - return file_.GetHash(hash); -} - std::string SaveFile::DebugString() const { return file_.DebugString(); } diff --git a/chromium/content/browser/download/save_file.h b/chromium/content/browser/download/save_file.h index 1dc82170ad0..69add3279da 100644 --- a/chromium/content/browser/download/save_file.h +++ b/chromium/content/browser/download/save_file.h @@ -38,7 +38,6 @@ class SaveFile { base::FilePath FullPath() const; bool InProgress() const; int64_t BytesSoFar() const; - bool GetHash(std::string* hash); std::string DebugString() const; // Accessors. diff --git a/chromium/content/browser/download/save_file_manager.cc b/chromium/content/browser/download/save_file_manager.cc index cf2e59ce3f1..527a7a18fbb 100644 --- a/chromium/content/browser/download/save_file_manager.cc +++ b/chromium/content/browser/download/save_file_manager.cc @@ -74,6 +74,10 @@ void SaveFileManager::SaveURL(SaveItemId save_item_id, SavePackage* save_package) { DCHECK_CURRENTLY_ON(BrowserThread::UI); + // Insert started saving job to tracking list. + DCHECK(packages_.find(save_item_id) == packages_.end()); + packages_[save_item_id] = save_package; + // Register a saving job. if (save_source == SaveFileCreateInfo::SAVE_FILE_FROM_NET) { DCHECK(url.is_valid()); @@ -201,19 +205,27 @@ void SaveFileManager::SaveFinished(SaveItemId save_item_id, << " save_package_id = " << save_package_id << " is_success = " << is_success; DCHECK_CURRENTLY_ON(BrowserThread::FILE); + + int64_t bytes_so_far = 0; SaveFile* save_file = LookupSaveFile(save_item_id); if (save_file != nullptr) { DCHECK(save_file->InProgress()); DVLOG(20) << " " << __FUNCTION__ << "()" << " save_file = " << save_file->DebugString(); - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&SaveFileManager::OnSaveFinished, this, save_item_id, - save_file->BytesSoFar(), is_success)); - + bytes_so_far = save_file->BytesSoFar(); save_file->Finish(); save_file->Detach(); + } else { + // We got called before StartSave - this should only happen if + // ResourceHandler failed before it got a chance to parse headers + // and metadata. + DCHECK(!is_success); } + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&SaveFileManager::OnSaveFinished, this, save_item_id, + bytes_so_far, is_success)); } // Notifications sent from the file thread and run on the UI thread. @@ -228,10 +240,6 @@ void SaveFileManager::OnStartSave(const SaveFileCreateInfo& info) { return; } - // Insert started saving job to tracking list. - DCHECK(packages_.find(info.save_item_id) == packages_.end()); - packages_[info.save_item_id] = save_package; - // Forward this message to SavePackage. save_package->StartSave(&info); } diff --git a/chromium/content/browser/download/save_file_manager.h b/chromium/content/browser/download/save_file_manager.h index 3f36206f43c..e99c2a23b8a 100644 --- a/chromium/content/browser/download/save_file_manager.h +++ b/chromium/content/browser/download/save_file_manager.h @@ -60,8 +60,8 @@ #include <stdint.h> #include <string> +#include <unordered_map> -#include "base/containers/hash_tables.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "content/browser/download/save_types.h" @@ -90,8 +90,10 @@ class SaveFileManager : public base::RefCountedThreadSafe<SaveFileManager> { // Lifetime management. CONTENT_EXPORT void Shutdown(); - // Save the specified URL. Called on the UI thread and forwarded to the - // ResourceDispatcherHostImpl on the IO thread. + // Save the specified URL. Caller has to guarantee that |save_package| will + // be alive until the call to RemoveSaveFile. Called on the UI thread (and in + // case of network downloads forwarded to the ResourceDispatcherHostImpl on + // the IO thread). void SaveURL(SaveItemId save_item_id, const GURL& url, const Referrer& referrer, @@ -204,12 +206,14 @@ class SaveFileManager : public base::RefCountedThreadSafe<SaveFileManager> { void ExecuteCancelSaveRequest(int render_process_id, int request_id); // A map from save_item_id into SaveFiles. - typedef base::hash_map<SaveItemId, SaveFile*> SaveFileMap; + using SaveFileMap = + std::unordered_map<SaveItemId, SaveFile*, SaveItemId::Hasher>; SaveFileMap save_file_map_; // Tracks which SavePackage to send data to, called only on UI thread. // SavePackageMap maps save item ids to their SavePackage. - typedef base::hash_map<SaveItemId, SavePackage*> SavePackageMap; + using SavePackageMap = + std::unordered_map<SaveItemId, SavePackage*, SaveItemId::Hasher>; SavePackageMap packages_; DISALLOW_COPY_AND_ASSIGN(SaveFileManager); diff --git a/chromium/content/browser/download/save_file_resource_handler.cc b/chromium/content/browser/download/save_file_resource_handler.cc index faf5605710a..7cba445aad5 100644 --- a/chromium/content/browser/download/save_file_resource_handler.cc +++ b/chromium/content/browser/download/save_file_resource_handler.cc @@ -85,7 +85,7 @@ bool SaveFileResourceHandler::OnReadCompleted(int bytes_read, bool* defer) { BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&SaveFileManager::UpdateSaveProgress, save_manager_, - save_item_id_, buffer, bytes_read)); + save_item_id_, base::RetainedRef(buffer), bytes_read)); return true; } diff --git a/chromium/content/browser/download/save_item.cc b/chromium/content/browser/download/save_item.cc index edea2632804..5fa262778e8 100644 --- a/chromium/content/browser/download/save_item.cc +++ b/chromium/content/browser/download/save_item.cc @@ -27,10 +27,12 @@ SaveItemId GetNextSaveItemId() { SaveItem::SaveItem(const GURL& url, const Referrer& referrer, SavePackage* package, - SaveFileCreateInfo::SaveFileSource save_source) + SaveFileCreateInfo::SaveFileSource save_source, + int frame_tree_node_id) : save_item_id_(GetNextSaveItemId()), url_(url), referrer_(referrer), + frame_tree_node_id_(frame_tree_node_id), total_bytes_(0), received_bytes_(0), state_(WAIT_START), diff --git a/chromium/content/browser/download/save_item.h b/chromium/content/browser/download/save_item.h index b0e655da71e..ef463953bf7 100644 --- a/chromium/content/browser/download/save_item.h +++ b/chromium/content/browser/download/save_item.h @@ -30,7 +30,8 @@ class SaveItem { SaveItem(const GURL& url, const Referrer& referrer, SavePackage* package, - SaveFileCreateInfo::SaveFileSource save_source); + SaveFileCreateInfo::SaveFileSource save_source, + int frame_tree_node_id); ~SaveItem(); @@ -61,6 +62,7 @@ class SaveItem { const base::FilePath& file_name() const { return file_name_; } const GURL& url() const { return url_; } const Referrer& referrer() const { return referrer_; } + int frame_tree_node_id() const { return frame_tree_node_id_; } int64_t total_bytes() const { return total_bytes_; } int64_t received_bytes() const { return received_bytes_; } bool has_final_name() const { return has_final_name_; } @@ -87,6 +89,10 @@ class SaveItem { GURL url_; Referrer referrer_; + // Frame tree node id, if this save item represents a frame + // (otherwise FrameTreeNode::kFrameTreeNodeInvalidID). + int frame_tree_node_id_; + // Total bytes expected. int64_t total_bytes_; diff --git a/chromium/content/browser/download/save_package.cc b/chromium/content/browser/download/save_package.cc index 5b7d6a6ead1..7c989856f6b 100644 --- a/chromium/content/browser/download/save_package.cc +++ b/chromium/content/browser/download/save_package.cc @@ -131,9 +131,6 @@ class SavePackageRequestHandle : public DownloadRequestHandleInterface { void PauseRequest() const override {} void ResumeRequest() const override {} void CancelRequest() const override {} - std::string DebugString() const override { - return "SavePackage DownloadRequestHandle"; - } private: base::WeakPtr<SavePackage> save_package_; @@ -354,10 +351,8 @@ void SavePackage::InitWithDownloadItem( SaveFileCreateInfo::SaveFileSource save_source = page_url_.SchemeIsFile() ? SaveFileCreateInfo::SAVE_FILE_FROM_FILE : SaveFileCreateInfo::SAVE_FILE_FROM_NET; - SaveItem* save_item = new SaveItem(page_url_, - Referrer(), - this, - save_source); + SaveItem* save_item = new SaveItem(page_url_, Referrer(), this, save_source, + FrameTreeNode::kFrameTreeNodeInvalidId); // Add this item to waiting list. waiting_item_queue_.push_back(save_item); all_save_items_count_ = 1; @@ -378,12 +373,10 @@ void SavePackage::OnMHTMLGenerated(int64_t size) { // TODO(rdsmith/benjhayden): Integrate canceling on DownloadItem // with SavePackage flow. if (download_->GetState() == DownloadItem::IN_PROGRESS) { - download_->SetTotalBytes(size); - download_->DestinationUpdate(size, 0, std::string()); // Must call OnAllDataSaved here in order for // GDataDownloadObserver::ShouldUpload() to return true. // ShouldCompleteDownload() may depend on the gdata uploader to finish. - download_->OnAllDataSaved(DownloadItem::kEmptyFileHash); + download_->OnAllDataSaved(size, scoped_ptr<crypto::SecureHash>()); } if (!download_manager_->GetDelegate()) { @@ -555,7 +548,7 @@ bool SavePackage::GenerateFileName(const std::string& disposition, // We have received a message from SaveFileManager about a new saving job. We // find a SaveItem and store it in our in_progress list. void SavePackage::StartSave(const SaveFileCreateInfo* info) { - DCHECK(info && !info->url.is_empty()); + DCHECK(info); SaveItemIdMap::iterator it = in_progress_items_.find(info->save_item_id); if (it == in_progress_items_.end()) { @@ -788,9 +781,9 @@ void SavePackage::Finish() { // with SavePackage flow. if (download_->GetState() == DownloadItem::IN_PROGRESS) { if (save_type_ != SAVE_PAGE_TYPE_AS_MHTML) { - download_->DestinationUpdate( - all_save_items_count_, CurrentSpeed(), std::string()); - download_->OnAllDataSaved(DownloadItem::kEmptyFileHash); + download_->DestinationUpdate(all_save_items_count_, CurrentSpeed()); + download_->OnAllDataSaved(all_save_items_count_, + scoped_ptr<crypto::SecureHash>()); } download_->MarkAsComplete(); } @@ -821,8 +814,7 @@ void SavePackage::SaveFinished(SaveItemId save_item_id, // TODO(rdsmith/benjhayden): Integrate canceling on DownloadItem // with SavePackage flow. if (download_ && (download_->GetState() == DownloadItem::IN_PROGRESS)) { - download_->DestinationUpdate( - completed_count(), CurrentSpeed(), std::string()); + download_->DestinationUpdate(completed_count(), CurrentSpeed()); } if (save_item->save_source() == SaveFileCreateInfo::SAVE_FILE_FROM_DOM && @@ -974,18 +966,28 @@ void SavePackage::GetSerializedHtmlWithLocalLinks() { if (successful_started_items_count != in_process_count()) return; - // Ask all frames for their serialized data. + // Try to serialize all the frames gathered during GetSavableResourceLinks. DCHECK_EQ(0, number_of_frames_pending_response_); FrameTree* frame_tree = static_cast<RenderFrameHostImpl*>(web_contents()->GetMainFrame()) ->frame_tree_node()->frame_tree(); for (const auto& item : frame_tree_node_id_to_save_item_) { - DCHECK(item.second); // SaveItem* != nullptr. int frame_tree_node_id = item.first; + SaveItem* save_item = item.second; + DCHECK(save_item); + FrameTreeNode* frame_tree_node = frame_tree->FindByID(frame_tree_node_id); - if (frame_tree_node) { + if (frame_tree_node && + frame_tree_node->current_frame_host()->IsRenderFrameLive()) { + // Ask the frame for HTML to be written to the associated SaveItem. GetSerializedHtmlWithLocalLinksForFrame(frame_tree_node); number_of_frames_pending_response_++; + } else { + // Notify SaveFileManager about the failure to save this SaveItem. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&SaveFileManager::SaveFinished, file_manager_, + save_item->id(), id(), false)); } } if (number_of_frames_pending_response_ == 0) { @@ -999,30 +1001,59 @@ void SavePackage::GetSerializedHtmlWithLocalLinksForFrame( FrameTreeNode* target_tree_node) { DCHECK(target_tree_node); int target_frame_tree_node_id = target_tree_node->frame_tree_node_id(); + RenderFrameHostImpl* target = target_tree_node->current_frame_host(); // Collect all saved success items. // SECURITY NOTE: We don't send *all* urls / local paths, but only // those that the given frame had access to already (because it contained // the savable resources / subframes associated with save items). std::map<GURL, base::FilePath> url_to_local_path; + std::map<int, base::FilePath> routing_id_to_local_path; auto it = frame_tree_node_id_to_contained_save_items_.find( target_frame_tree_node_id); if (it != frame_tree_node_id_to_contained_save_items_.end()) { for (SaveItem* save_item : it->second) { - DCHECK(save_item->has_final_name()); + // Skip items that failed to save. + if (!save_item->has_final_name()) { + DCHECK_EQ(SaveItem::SaveState::COMPLETE, save_item->state()); + DCHECK(!save_item->success()); + continue; + } + + // Calculate the relative path for referring to the |save_item|. base::FilePath local_path(base::FilePath::kCurrentDirectory); if (target_tree_node->IsMainFrame()) { local_path = local_path.Append(saved_main_directory_path_.BaseName()); } local_path = local_path.Append(save_item->file_name()); - url_to_local_path[save_item->url()] = local_path; + + // Insert the link into |url_to_local_path| or |routing_id_to_local_path|. + if (save_item->save_source() != SaveFileCreateInfo::SAVE_FILE_FROM_DOM) { + DCHECK_EQ(FrameTreeNode::kFrameTreeNodeInvalidId, + save_item->frame_tree_node_id()); + url_to_local_path[save_item->url()] = local_path; + } else { + FrameTreeNode* save_item_frame_tree_node = + target_tree_node->frame_tree()->FindByID( + save_item->frame_tree_node_id()); + if (!save_item_frame_tree_node) { + // crbug.com/541354: Raciness when saving a dynamically changing page. + continue; + } + + int routing_id = + save_item_frame_tree_node->render_manager() + ->GetRoutingIdForSiteInstance(target->GetSiteInstance()); + DCHECK_NE(MSG_ROUTING_NONE, routing_id); + + routing_id_to_local_path[routing_id] = local_path; + } } } // Ask target frame to serialize itself. - RenderFrameHostImpl* target = target_tree_node->current_frame_host(); target->Send(new FrameMsg_GetSerializedHtmlWithLocalLinks( - target->GetRoutingID(), url_to_local_path)); + target->GetRoutingID(), url_to_local_path, routing_id_to_local_path)); } // Process the serialized HTML content data of a specified frame @@ -1069,7 +1100,8 @@ void SavePackage::OnSerializedHtmlWithLocalLinksResponse( BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&SaveFileManager::UpdateSaveProgress, file_manager_, - save_item->id(), new_data, static_cast<int>(data.size()))); + save_item->id(), base::RetainedRef(new_data), + static_cast<int>(data.size()))); } // Current frame is completed saving, call finish in file thread. @@ -1150,14 +1182,14 @@ void SavePackage::OnSavableResourceLinksResponse( SaveItem* SavePackage::CreatePendingSaveItem( int container_frame_tree_node_id, + int save_item_frame_tree_node_id, const GURL& url, const Referrer& referrer, SaveFileCreateInfo::SaveFileSource save_source) { - DCHECK(url.is_valid()); // |url| should be validated by the callers. - SaveItem* save_item; Referrer sanitized_referrer = Referrer::SanitizeForRequest(url, referrer); - save_item = new SaveItem(url, sanitized_referrer, this, save_source); + save_item = new SaveItem(url, sanitized_referrer, this, save_source, + save_item_frame_tree_node_id); waiting_item_queue_.push_back(save_item); frame_tree_node_id_to_contained_save_items_[container_frame_tree_node_id] @@ -1167,6 +1199,7 @@ SaveItem* SavePackage::CreatePendingSaveItem( SaveItem* SavePackage::CreatePendingSaveItemDeduplicatingByUrl( int container_frame_tree_node_id, + int save_item_frame_tree_node_id, const GURL& url, const Referrer& referrer, SaveFileCreateInfo::SaveFileSource save_source) { @@ -1182,7 +1215,8 @@ SaveItem* SavePackage::CreatePendingSaveItemDeduplicatingByUrl( frame_tree_node_id_to_contained_save_items_[container_frame_tree_node_id] .push_back(save_item); } else { - save_item = CreatePendingSaveItem(container_frame_tree_node_id, url, + save_item = CreatePendingSaveItem(container_frame_tree_node_id, + save_item_frame_tree_node_id, url, referrer, save_source); url_to_save_item_[url] = save_item; } @@ -1199,19 +1233,17 @@ void SavePackage::EnqueueSavableResource(int container_frame_tree_node_id, SaveFileCreateInfo::SaveFileSource save_source = url.SchemeIsFile() ? SaveFileCreateInfo::SAVE_FILE_FROM_FILE : SaveFileCreateInfo::SAVE_FILE_FROM_NET; - CreatePendingSaveItemDeduplicatingByUrl(container_frame_tree_node_id, url, - referrer, save_source); + CreatePendingSaveItemDeduplicatingByUrl( + container_frame_tree_node_id, FrameTreeNode::kFrameTreeNodeInvalidId, url, + referrer, save_source); } void SavePackage::EnqueueFrame(int container_frame_tree_node_id, int frame_tree_node_id, const GURL& frame_original_url) { - if (!frame_original_url.is_valid()) - return; - - SaveItem* save_item = - CreatePendingSaveItem(container_frame_tree_node_id, frame_original_url, - Referrer(), SaveFileCreateInfo::SAVE_FILE_FROM_DOM); + SaveItem* save_item = CreatePendingSaveItem( + container_frame_tree_node_id, frame_tree_node_id, frame_original_url, + Referrer(), SaveFileCreateInfo::SAVE_FILE_FROM_DOM); DCHECK(save_item); frame_tree_node_id_to_save_item_[frame_tree_node_id] = save_item; } @@ -1261,8 +1293,7 @@ void SavePackage::CompleteSavableResourceLinksResponse() { base::FilePath SavePackage::GetSuggestedNameForSaveAs( bool can_save_as_complete, - const std::string& contents_mime_type, - const std::string& accept_langs) { + const std::string& contents_mime_type) { base::FilePath name_with_proper_ext = base::FilePath::FromUTF16Unsafe(title_); // If the page's title matches its URL, use the URL. Try to use the last path @@ -1274,7 +1305,7 @@ base::FilePath SavePackage::GetSuggestedNameForSaveAs( // back to a URL, and if it matches the original page URL, we know the page // had no title (or had a title equal to its URL, which is fine to treat // similarly). - if (title_ == url_formatter::FormatUrl(page_url_, accept_langs)) { + if (title_ == url_formatter::FormatUrl(page_url_)) { std::string url_path; if (!page_url_.SchemeIs(url::kDataScheme)) { std::vector<std::string> url_parts = base::SplitString( @@ -1374,23 +1405,17 @@ void SavePackage::GetSaveInfo() { &download_save_dir, &skip_dir_check); } std::string mime_type = web_contents()->GetContentsMimeType(); - std::string accept_languages = - GetContentClient()->browser()->GetAcceptLangs( - web_contents()->GetBrowserContext()); - BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&SavePackage::CreateDirectoryOnFileThread, this, - website_save_dir, download_save_dir, skip_dir_check, - mime_type, accept_languages)); + website_save_dir, download_save_dir, skip_dir_check, mime_type)); } void SavePackage::CreateDirectoryOnFileThread( const base::FilePath& website_save_dir, const base::FilePath& download_save_dir, bool skip_dir_check, - const std::string& mime_type, - const std::string& accept_langs) { + const std::string& mime_type) { base::FilePath save_dir; // If the default html/websites save folder doesn't exist... // We skip the directory check for gdata directories on ChromeOS. @@ -1408,7 +1433,7 @@ void SavePackage::CreateDirectoryOnFileThread( bool can_save_as_complete = CanSaveAsComplete(mime_type); base::FilePath suggested_filename = GetSuggestedNameForSaveAs( - can_save_as_complete, mime_type, accept_langs); + can_save_as_complete, mime_type); base::FilePath::StringType pure_file_name = suggested_filename.RemoveExtension().BaseName().value(); base::FilePath::StringType file_name_ext = suggested_filename.Extension(); diff --git a/chromium/content/browser/download/save_package.h b/chromium/content/browser/download/save_package.h index eabf65717a0..5b0e958c3a0 100644 --- a/chromium/content/browser/download/save_package.h +++ b/chromium/content/browser/download/save_package.h @@ -12,9 +12,9 @@ #include <map> #include <set> #include <string> +#include <unordered_map> #include <vector> -#include "base/containers/hash_tables.h" #include "base/files/file_path.h" #include "base/gtest_prod_util.h" #include "base/macros.h" @@ -206,6 +206,7 @@ class CONTENT_EXPORT SavePackage // Helper for finding or creating a SaveItem with the given parameters. SaveItem* CreatePendingSaveItem( int container_frame_tree_node_id, + int save_item_frame_tree_node_id, const GURL& url, const Referrer& referrer, SaveFileCreateInfo::SaveFileSource save_source); @@ -214,6 +215,7 @@ class CONTENT_EXPORT SavePackage // creating a SaveItem with the given parameters. SaveItem* CreatePendingSaveItemDeduplicatingByUrl( int container_frame_tree_node_id, + int save_item_frame_tree_node_id, const GURL& url, const Referrer& referrer, SaveFileCreateInfo::SaveFileSource save_source); @@ -263,8 +265,7 @@ class CONTENT_EXPORT SavePackage void CreateDirectoryOnFileThread(const base::FilePath& website_save_dir, const base::FilePath& download_save_dir, bool skip_dir_check, - const std::string& mime_type, - const std::string& accept_langs); + const std::string& mime_type); void ContinueGetSaveInfo(const base::FilePath& suggested_path, bool can_save_as_complete); void OnPathPicked( @@ -273,7 +274,8 @@ class CONTENT_EXPORT SavePackage const SavePackageDownloadCreatedCallback& cb); // Map from SaveItem::id() (aka save_item_id) into a SaveItem. - typedef base::hash_map<SaveItemId, SaveItem*> SaveItemIdMap; + using SaveItemIdMap = + std::unordered_map<SaveItemId, SaveItem*, SaveItemId::Hasher>; // in_progress_items_ is map of all saving job in in-progress state. SaveItemIdMap in_progress_items_; // saved_failed_items_ is map of all saving job which are failed. @@ -302,8 +304,7 @@ class CONTENT_EXPORT SavePackage // suggested name is determined by the web document's title. base::FilePath GetSuggestedNameForSaveAs( bool can_save_as_complete, - const std::string& contents_mime_type, - const std::string& accept_langs); + const std::string& contents_mime_type); // Ensures that the file name has a proper extension for HTML by adding ".htm" // if necessary. @@ -319,7 +320,7 @@ class CONTENT_EXPORT SavePackage static const base::FilePath::CharType* ExtensionForMimeType( const std::string& contents_mime_type); - typedef std::deque<SaveItem*> SaveItemQueue; + using SaveItemQueue = std::deque<SaveItem*>; // A queue for items we are about to start saving. SaveItemQueue waiting_item_queue_; @@ -333,21 +334,22 @@ class CONTENT_EXPORT SavePackage // OnSerializedHtmlWithLocalLinksResponse) to the right SaveItem. // Note that |frame_tree_node_id_to_save_item_| does NOT own SaveItems - they // remain owned by waiting_item_queue_, in_progress_items_, etc. - base::hash_map<int, SaveItem*> frame_tree_node_id_to_save_item_; + std::unordered_map<int, SaveItem*> frame_tree_node_id_to_save_item_; // Used to limit which local paths get exposed to which frames // (i.e. to prevent information disclosure to oop frames). // Note that |frame_tree_node_id_to_contained_save_items_| does NOT own // SaveItems - they remain owned by waiting_item_queue_, in_progress_items_, // etc. - base::hash_map<int, std::vector<SaveItem*>> + std::unordered_map<int, std::vector<SaveItem*>> frame_tree_node_id_to_contained_save_items_; // Number of frames that we still need to get a response from. int number_of_frames_pending_response_; // saved_success_items_ is map of all saving job which are successfully saved. - base::hash_map<SaveItemId, SaveItem*> saved_success_items_; + std::unordered_map<SaveItemId, SaveItem*, SaveItemId::Hasher> + saved_success_items_; // Non-owning pointer for handling file writing on the file thread. SaveFileManager* file_manager_; @@ -392,7 +394,8 @@ class CONTENT_EXPORT SavePackage // This set is used to eliminate duplicated file names in saving directory. FileNameSet file_name_set_; - typedef base::hash_map<base::FilePath::StringType, uint32_t> FileNameCountMap; + using FileNameCountMap = + std::unordered_map<base::FilePath::StringType, uint32_t>; // This map is used to track serial number for specified filename. FileNameCountMap file_name_count_map_; diff --git a/chromium/content/browser/download/save_package_unittest.cc b/chromium/content/browser/download/save_package_unittest.cc index fc89b6d1806..c426772a820 100644 --- a/chromium/content/browser/download/save_package_unittest.cc +++ b/chromium/content/browser/download/save_package_unittest.cc @@ -406,8 +406,7 @@ TEST_F(SavePackageTest, MAYBE_TestSuggestedSaveNames) { save_package->title_ = kSuggestedSaveNames[i].page_title; base::FilePath save_name = save_package->GetSuggestedNameForSaveAs( - kSuggestedSaveNames[i].ensure_html_extension, - std::string(), std::string()); + kSuggestedSaveNames[i].ensure_html_extension, std::string()); EXPECT_EQ(kSuggestedSaveNames[i].expected_name, save_name.value()) << "Test case " << i; } diff --git a/chromium/content/browser/download/save_types.cc b/chromium/content/browser/download/save_types.cc index 9c4972a8854..0dd760562c1 100644 --- a/chromium/content/browser/download/save_types.cc +++ b/chromium/content/browser/download/save_types.cc @@ -45,6 +45,9 @@ SaveFileCreateInfo::SaveFileCreateInfo(const GURL& url, total_bytes(total_bytes), save_source(SaveFileCreateInfo::SAVE_FILE_FROM_NET) {} +SaveFileCreateInfo::SaveFileCreateInfo(const SaveFileCreateInfo& other) = + default; + SaveFileCreateInfo::~SaveFileCreateInfo() {} } // namespace content diff --git a/chromium/content/browser/download/save_types.h b/chromium/content/browser/download/save_types.h index 83d890b8dc9..9d61a9a70a0 100644 --- a/chromium/content/browser/download/save_types.h +++ b/chromium/content/browser/download/save_types.h @@ -13,16 +13,16 @@ #include <vector> #include "base/files/file_path.h" -#include "content/common/id_type.h" +#include "gpu/command_buffer/common/id_type.h" #include "url/gurl.h" namespace content { class SavePackage; -using SavePackageId = IdType32<SavePackage>; +using SavePackageId = gpu::IdType32<SavePackage>; class SaveItem; -using SaveItemId = IdType32<SaveItem>; +using SaveItemId = gpu::IdType32<SaveItem>; // Map from save_item_id into final file path. using FinalNamesMap = std::map<SaveItemId, base::FilePath>; @@ -63,6 +63,8 @@ struct SaveFileCreateInfo { const std::string& content_disposition, int64_t total_bytes); + SaveFileCreateInfo(const SaveFileCreateInfo& other); + ~SaveFileCreateInfo(); // SaveItem fields. diff --git a/chromium/content/browser/download/url_downloader.cc b/chromium/content/browser/download/url_downloader.cc index b178a1d4b26..274de905d8c 100644 --- a/chromium/content/browser/download/url_downloader.cc +++ b/chromium/content/browser/download/url_downloader.cc @@ -60,7 +60,6 @@ class UrlDownloader::RequestHandle : public DownloadRequestHandleInterface { downloader_task_runner_->PostTask( FROM_HERE, base::Bind(&UrlDownloader::CancelRequest, downloader_)); } - std::string DebugString() const override { return std::string(); } private: base::WeakPtr<UrlDownloader> downloader_; @@ -74,62 +73,32 @@ class UrlDownloader::RequestHandle : public DownloadRequestHandleInterface { scoped_ptr<UrlDownloader> UrlDownloader::BeginDownload( base::WeakPtr<DownloadManagerImpl> download_manager, scoped_ptr<net::URLRequest> request, - const Referrer& referrer, - bool prefer_cache, - scoped_ptr<DownloadSaveInfo> save_info, - uint32_t download_id, - const DownloadUrlParameters::OnStartedCallback& started_callback) { + const Referrer& referrer) { if (!referrer.url.is_valid()) request->SetReferrer(std::string()); else request->SetReferrer(referrer.url.spec()); - int extra_load_flags = net::LOAD_NORMAL; - if (prefer_cache) { - // If there is upload data attached, only retrieve from cache because there - // is no current mechanism to prompt the user for their consent for a - // re-post. For GETs, try to retrieve data from the cache and skip - // validating the entry if present. - if (request->get_upload() != NULL) - extra_load_flags |= net::LOAD_ONLY_FROM_CACHE; - else - extra_load_flags |= net::LOAD_PREFERRING_CACHE; - } else { - extra_load_flags |= net::LOAD_DISABLE_CACHE; - } - request->SetLoadFlags(request->load_flags() | extra_load_flags); - if (request->url().SchemeIs(url::kBlobScheme)) return nullptr; // From this point forward, the |UrlDownloader| is responsible for // |started_callback|. scoped_ptr<UrlDownloader> downloader( - new UrlDownloader(std::move(request), download_manager, - std::move(save_info), download_id, started_callback)); + new UrlDownloader(std::move(request), download_manager)); downloader->Start(); return downloader; } -UrlDownloader::UrlDownloader( - scoped_ptr<net::URLRequest> request, - base::WeakPtr<DownloadManagerImpl> manager, - scoped_ptr<DownloadSaveInfo> save_info, - uint32_t download_id, - const DownloadUrlParameters::OnStartedCallback& on_started_callback) +UrlDownloader::UrlDownloader(scoped_ptr<net::URLRequest> request, + base::WeakPtr<DownloadManagerImpl> manager) : request_(std::move(request)), manager_(manager), - download_id_(download_id), - on_started_callback_(on_started_callback), - handler_( - request_.get(), - std::move(save_info), - base::Bind(&UrlDownloader::ResumeReading, base::Unretained(this))), + core_(request_.get(), this), weak_ptr_factory_(this) {} UrlDownloader::~UrlDownloader() { - CallStartedCallbackOnFailure(DOWNLOAD_INTERRUPT_REASON_USER_CANCELED); } void UrlDownloader::Start() { @@ -146,7 +115,15 @@ void UrlDownloader::OnReceivedRedirect(net::URLRequest* request, const net::RedirectInfo& redirect_info, bool* defer_redirect) { DVLOG(1) << "OnReceivedRedirect: " << request_->url().spec(); - request_->CancelWithError(net::ERR_ABORTED); + + // We are going to block redirects even if DownloadRequestCore allows it. No + // redirects are expected for download requests that are made without a + // renderer, which are currently exclusively resumption requests. Since there + // is no security policy being applied here, it's safer to block redirects and + // revisit if some previously unknown legitimate use case arises for redirects + // while resuming. + core_.OnWillAbort(DOWNLOAD_INTERRUPT_REASON_SERVER_UNREACHABLE); + request_->CancelWithError(net::ERR_UNSAFE_REDIRECT); } void UrlDownloader::OnResponseStarted(net::URLRequest* request) { @@ -157,22 +134,7 @@ void UrlDownloader::OnResponseStarted(net::URLRequest* request) { return; } - scoped_ptr<DownloadCreateInfo> create_info; - scoped_ptr<ByteStreamReader> stream_reader; - - handler_.OnResponseStarted(&create_info, &stream_reader); - - create_info->download_id = download_id_; - create_info->request_handle.reset( - new RequestHandle(weak_ptr_factory_.GetWeakPtr(), manager_, - base::SequencedTaskRunnerHandle::Get())); - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&DownloadManagerImpl::StartDownload, manager_, - base::Passed(&create_info), base::Passed(&stream_reader), - base::ResetAndReturn(&on_started_callback_))); - - if (request_->status().is_success()) + if (core_.OnResponseStarted(std::string())) StartReading(false); // Read the first chunk. else ResponseCompleted(); @@ -186,7 +148,7 @@ void UrlDownloader::StartReading(bool is_continuation) { // doesn't use the buffer. scoped_refptr<net::IOBuffer> buf; int buf_size; - if (!handler_.OnWillRead(&buf, &buf_size, -1)) { + if (!core_.OnWillRead(&buf, &buf_size, -1)) { request_->CancelWithError(net::ERR_ABORTED); base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&UrlDownloader::ResponseCompleted, @@ -229,7 +191,7 @@ void UrlDownloader::OnReadCompleted(net::URLRequest* request, int bytes_read) { DCHECK(request_->status().is_success()); bool defer = false; - if (!handler_.OnReadCompleted(bytes_read, &defer)) { + if (!core_.OnReadCompleted(bytes_read, &defer)) { request_->CancelWithError(net::ERR_ABORTED); return; } else if (defer) { @@ -251,38 +213,43 @@ void UrlDownloader::OnReadCompleted(net::URLRequest* request, int bytes_read) { void UrlDownloader::ResponseCompleted() { DVLOG(1) << "ResponseCompleted: " << request_->url().spec(); - handler_.OnResponseCompleted(request_->status()); - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&DownloadManagerImpl::RemoveUrlDownloader, manager_, this)); + core_.OnResponseCompleted(request_->status()); + Destroy(); } -void UrlDownloader::ResumeReading() { - if (request_->status().is_success()) { - StartReading(false); // Read the next chunk (OK to complete synchronously). - } else { - ResponseCompleted(); - } +void UrlDownloader::OnStart( + scoped_ptr<DownloadCreateInfo> create_info, + scoped_ptr<ByteStreamReader> stream_reader, + const DownloadUrlParameters::OnStartedCallback& callback) { + create_info->request_handle.reset( + new RequestHandle(weak_ptr_factory_.GetWeakPtr(), manager_, + base::SequencedTaskRunnerHandle::Get())); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&DownloadManagerImpl::StartDownload, + manager_, base::Passed(&create_info), + base::Passed(&stream_reader), callback)); } -void UrlDownloader::CallStartedCallbackOnFailure( - DownloadInterruptReason result) { - if (on_started_callback_.is_null()) - return; - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(base::ResetAndReturn(&on_started_callback_), nullptr, result)); +void UrlDownloader::OnReadyToRead() { + if (request_->status().is_success()) + StartReading(false); // Read the next chunk (OK to complete synchronously). + else + ResponseCompleted(); } void UrlDownloader::PauseRequest() { - handler_.PauseRequest(); + core_.PauseRequest(); } void UrlDownloader::ResumeRequest() { - handler_.ResumeRequest(); + core_.ResumeRequest(); } void UrlDownloader::CancelRequest() { + Destroy(); +} + +void UrlDownloader::Destroy() { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&DownloadManagerImpl::RemoveUrlDownloader, manager_, this)); diff --git a/chromium/content/browser/download/url_downloader.h b/chromium/content/browser/download/url_downloader.h index 787feb4c91b..475d97bfbc6 100644 --- a/chromium/content/browser/download/url_downloader.h +++ b/chromium/content/browser/download/url_downloader.h @@ -10,6 +10,7 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "content/browser/download/download_request_core.h" +#include "content/public/browser/download_interrupt_reasons.h" #include "content/public/browser/download_save_info.h" #include "content/public/browser/download_url_parameters.h" #include "content/public/common/referrer.h" @@ -17,26 +18,26 @@ #include "net/url_request/url_request.h" namespace content { +class ByteStreamReader; +struct DownloadCreateInfo; class DownloadManagerImpl; -class UrlDownloader : public net::URLRequest::Delegate { +class UrlDownloader : public net::URLRequest::Delegate, + public DownloadRequestCore::Delegate { public: - UrlDownloader( - scoped_ptr<net::URLRequest> request, - base::WeakPtr<DownloadManagerImpl> manager, - scoped_ptr<DownloadSaveInfo> save_info, - uint32_t download_id, - const DownloadUrlParameters::OnStartedCallback& on_started_callback); + UrlDownloader(scoped_ptr<net::URLRequest> request, + base::WeakPtr<DownloadManagerImpl> manager); ~UrlDownloader() override; static scoped_ptr<UrlDownloader> BeginDownload( base::WeakPtr<DownloadManagerImpl> download_manager, scoped_ptr<net::URLRequest> request, - const Referrer& referrer, - bool prefer_cache, - scoped_ptr<DownloadSaveInfo> save_info, - uint32_t download_id, - const DownloadUrlParameters::OnStartedCallback& started_callback); + const Referrer& referrer); + + private: + class RequestHandle; + + void Start(); // URLRequest::Delegate: void OnReceivedRedirect(net::URLRequest* request, @@ -48,24 +49,25 @@ class UrlDownloader : public net::URLRequest::Delegate { void StartReading(bool is_continuation); void ResponseCompleted(); - void Start(); - void ResumeReading(); - - void CallStartedCallbackOnFailure(DownloadInterruptReason result); - - private: - class RequestHandle; + // DownloadRequestCore::Delegate + void OnStart( + scoped_ptr<DownloadCreateInfo> download_create_info, + scoped_ptr<ByteStreamReader> stream_reader, + const DownloadUrlParameters::OnStartedCallback& callback) override; + void OnReadyToRead() override; void PauseRequest(); void ResumeRequest(); void CancelRequest(); + // Called when the UrlDownloader is done with the request. Posts a task to + // remove itself from its download manager, which in turn would cause the + // UrlDownloader to be freed. + void Destroy(); + scoped_ptr<net::URLRequest> request_; base::WeakPtr<DownloadManagerImpl> manager_; - uint32_t download_id_; - DownloadUrlParameters::OnStartedCallback on_started_callback_; - - DownloadRequestCore handler_; + DownloadRequestCore core_; base::WeakPtrFactory<UrlDownloader> weak_ptr_factory_; }; |