summaryrefslogtreecommitdiff
path: root/chromium/content/browser/download
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2016-05-09 14:22:11 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2016-05-09 15:11:45 +0000
commit2ddb2d3e14eef3de7dbd0cef553d669b9ac2361c (patch)
treee75f511546c5fd1a173e87c1f9fb11d7ac8d1af3 /chromium/content/browser/download
parenta4f3d46271c57e8155ba912df46a05559d14726e (diff)
downloadqtwebengine-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')
-rw-r--r--chromium/content/browser/download/OWNERS2
-rw-r--r--chromium/content/browser/download/base_file.cc201
-rw-r--r--chromium/content/browser/download/base_file.h190
-rw-r--r--chromium/content/browser/download/base_file_linux.cc7
-rw-r--r--chromium/content/browser/download/base_file_mac.cc9
-rw-r--r--chromium/content/browser/download/base_file_unittest.cc459
-rw-r--r--chromium/content/browser/download/base_file_win.cc11
-rw-r--r--chromium/content/browser/download/docs/save-page-as.md137
-rw-r--r--chromium/content/browser/download/download_browsertest.cc999
-rw-r--r--chromium/content/browser/download/download_create_info.cc21
-rw-r--r--chromium/content/browser/download/download_create_info.h50
-rw-r--r--chromium/content/browser/download/download_destination_observer.h44
-rw-r--r--chromium/content/browser/download/download_file.h29
-rw-r--r--chromium/content/browser/download/download_file_factory.cc13
-rw-r--r--chromium/content/browser/download/download_file_factory.h10
-rw-r--r--chromium/content/browser/download/download_file_impl.cc158
-rw-r--r--chromium/content/browser/download/download_file_impl.h66
-rw-r--r--chromium/content/browser/download/download_file_unittest.cc221
-rw-r--r--chromium/content/browser/download/download_interrupt_reasons_impl.cc3
-rw-r--r--chromium/content/browser/download/download_item_factory.h2
-rw-r--r--chromium/content/browser/download/download_item_impl.cc1061
-rw-r--r--chromium/content/browser/download/download_item_impl.h392
-rw-r--r--chromium/content/browser/download/download_item_impl_delegate.cc5
-rw-r--r--chromium/content/browser/download/download_item_impl_delegate.h5
-rw-r--r--chromium/content/browser/download/download_item_impl_unittest.cc1180
-rw-r--r--chromium/content/browser/download/download_manager_impl.cc305
-rw-r--r--chromium/content/browser/download/download_manager_impl.h35
-rw-r--r--chromium/content/browser/download/download_manager_impl_unittest.cc179
-rw-r--r--chromium/content/browser/download/download_net_log_parameters.cc9
-rw-r--r--chromium/content/browser/download/download_net_log_parameters.h3
-rw-r--r--chromium/content/browser/download/download_request_core.cc527
-rw-r--r--chromium/content/browser/download/download_request_core.h88
-rw-r--r--chromium/content/browser/download/download_request_handle.cc86
-rw-r--r--chromium/content/browser/download/download_request_handle.h29
-rw-r--r--chromium/content/browser/download/download_resource_handler.cc105
-rw-r--r--chromium/content/browser/download/download_resource_handler.h25
-rw-r--r--chromium/content/browser/download/drag_download_file.cc4
-rw-r--r--chromium/content/browser/download/mhtml_generation_manager.cc5
-rw-r--r--chromium/content/browser/download/mock_download_file.h9
-rw-r--r--chromium/content/browser/download/save_file.cc23
-rw-r--r--chromium/content/browser/download/save_file.h1
-rw-r--r--chromium/content/browser/download/save_file_manager.cc26
-rw-r--r--chromium/content/browser/download/save_file_manager.h14
-rw-r--r--chromium/content/browser/download/save_file_resource_handler.cc2
-rw-r--r--chromium/content/browser/download/save_item.cc4
-rw-r--r--chromium/content/browser/download/save_item.h8
-rw-r--r--chromium/content/browser/download/save_package.cc121
-rw-r--r--chromium/content/browser/download/save_package.h25
-rw-r--r--chromium/content/browser/download/save_package_unittest.cc3
-rw-r--r--chromium/content/browser/download/save_types.cc3
-rw-r--r--chromium/content/browser/download/save_types.h8
-rw-r--r--chromium/content/browser/download/url_downloader.cc115
-rw-r--r--chromium/content/browser/download/url_downloader.h48
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_;
};