diff options
author | asanka@chromium.org <asanka@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-25 01:23:48 +0000 |
---|---|---|
committer | asanka@chromium.org <asanka@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-25 01:23:48 +0000 |
commit | 192d68082fcd74a8de473ed931a9931d28a66bc0 (patch) | |
tree | a4ad2578bc9c14bd13b4f21ae4b9c204c5fd33ba /content/browser | |
parent | a33efc040cc07ab8526c9701a69103489862b455 (diff) | |
download | chromium_src-192d68082fcd74a8de473ed931a9931d28a66bc0.zip chromium_src-192d68082fcd74a8de473ed931a9931d28a66bc0.tar.gz chromium_src-192d68082fcd74a8de473ed931a9931d28a66bc0.tar.bz2 |
Refactor BaseFile methods to return a DownloadInterruptReason instead of a net::Error.
Also split the implementation into platform specific files.
BUG=157245
Review URL: https://chromiumcodereview.appspot.com/11238044
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@163983 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser')
-rw-r--r-- | content/browser/download/base_file.cc | 401 | ||||
-rw-r--r-- | content/browser/download/base_file.h | 79 | ||||
-rw-r--r-- | content/browser/download/base_file_linux.cc | 15 | ||||
-rw-r--r-- | content/browser/download/base_file_mac.cc | 16 | ||||
-rw-r--r-- | content/browser/download/base_file_posix.cc | 40 | ||||
-rw-r--r-- | content/browser/download/base_file_unittest.cc | 166 | ||||
-rw-r--r-- | content/browser/download/base_file_win.cc | 189 | ||||
-rw-r--r-- | content/browser/download/download_file_impl.cc | 23 | ||||
-rw-r--r-- | content/browser/download/download_net_log_parameters.cc | 15 | ||||
-rw-r--r-- | content/browser/download/download_net_log_parameters.h | 5 | ||||
-rw-r--r-- | content/browser/download/save_file.cc | 7 | ||||
-rw-r--r-- | content/browser/download/save_file.h | 7 | ||||
-rw-r--r-- | content/browser/download/save_file_manager.cc | 4 |
13 files changed, 544 insertions, 423 deletions
diff --git a/content/browser/download/base_file.cc b/content/browser/download/base_file.cc index 6cbe98e..65fd7a1 100644 --- a/content/browser/download/base_file.cc +++ b/content/browser/download/base_file.cc @@ -11,7 +11,7 @@ #include "base/pickle.h" #include "base/stringprintf.h" #include "base/threading/thread_restrictions.h" -#include "base/utf_string_conversions.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" @@ -20,186 +20,8 @@ #include "net/base/file_stream.h" #include "net/base/net_errors.h" -#if defined(OS_WIN) -#include <windows.h> -#include <shellapi.h> - -#include "content/browser/safe_util_win.h" -#elif defined(OS_MACOSX) -#include "content/browser/download/file_metadata_mac.h" -#elif defined(OS_LINUX) -#include "content/browser/download/file_metadata_linux.h" -#endif - using content::BrowserThread; -namespace { - -#define LOG_ERROR(o, e) \ - LogError(__FILE__, __LINE__, __FUNCTION__, bound_net_log_, o, e) - -// Logs the value and passes error on through, converting to a |net::Error|. -// Returns |ERR_UNEXPECTED| if the value is not in the enum. -net::Error LogError(const char* file, - int line, - const char* func, - const net::BoundNetLog& bound_net_log, - const char* operation, - int error) { - const char* err_string = ""; - net::Error net_error = net::OK; - -#define NET_ERROR(label, value) \ - case net::ERR_##label: \ - err_string = #label; \ - net_error = net::ERR_##label; \ - break; - - switch (error) { - case net::OK: - return net::OK; - -#include "net/base/net_error_list.h" - - default: - err_string = "Unexpected enum value"; - net_error = net::ERR_UNEXPECTED; - break; - } - -#undef NET_ERROR - - VLOG(1) << " " << func << "(): " << operation - << "() returned error " << error << " (" << err_string << ")"; - - bound_net_log.AddEvent( - net::NetLog::TYPE_DOWNLOAD_FILE_ERROR, - base::Bind(&download_net_logs::FileErrorCallback, operation, net_error)); - - return net_error; -} - -#if defined(OS_WIN) - -#define SHFILE_TO_NET_ERROR(symbol, value, mapping, description) \ - case value: return net::ERR_##mapping; - -// Maps the result of a call to |SHFileOperation()| onto a |net::Error|. -// -// These return codes are *old* (as in, DOS era), and specific to -// |SHFileOperation()|. -// They do not appear in any windows header. -// -// See http://msdn.microsoft.com/en-us/library/bb762164(VS.85).aspx. -net::Error MapShFileOperationCodes(int code) { - // Check these pre-Win32 error codes first, then check for matches - // in Winerror.h. - - switch (code) { - // Error Code, Value, Platform Error Mapping, Meaning - SHFILE_TO_NET_ERROR(DE_SAMEFILE, 0x71, FILE_EXISTS, - "The source and destination files are the same file.") - SHFILE_TO_NET_ERROR(DE_OPCANCELLED, 0x75, ABORTED, - "The operation was canceled by the user, or silently canceled if " - "the appropriate flags were supplied to SHFileOperation.") - SHFILE_TO_NET_ERROR(DE_ACCESSDENIEDSRC, 0x78, ACCESS_DENIED, - "Security settings denied access to the source.") - SHFILE_TO_NET_ERROR(DE_PATHTOODEEP, 0x79, FILE_PATH_TOO_LONG, - "The source or destination path exceeded or would exceed MAX_PATH.") - SHFILE_TO_NET_ERROR(DE_INVALIDFILES, 0x7C, FILE_NOT_FOUND, - "The path in the source or destination or both was invalid.") - SHFILE_TO_NET_ERROR(DE_FLDDESTISFILE, 0x7E, FILE_EXISTS, - "The destination path is an existing file.") - SHFILE_TO_NET_ERROR(DE_FILEDESTISFLD, 0x80, FILE_EXISTS, - "The destination path is an existing folder.") - SHFILE_TO_NET_ERROR(DE_FILENAMETOOLONG, 0x81, FILE_PATH_TOO_LONG, - "The name of the file exceeds MAX_PATH.") - SHFILE_TO_NET_ERROR(DE_DEST_IS_CDROM, 0x82, ACCESS_DENIED, - "The destination is a read-only CD-ROM, possibly unformatted.") - SHFILE_TO_NET_ERROR(DE_DEST_IS_DVD, 0x83, ACCESS_DENIED, - "The destination is a read-only DVD, possibly unformatted.") - SHFILE_TO_NET_ERROR(DE_DEST_IS_CDRECORD, 0x84, ACCESS_DENIED, - "The destination is a writable CD-ROM, possibly unformatted.") - SHFILE_TO_NET_ERROR(DE_FILE_TOO_LARGE, 0x85, FILE_TOO_BIG, - "The file involved in the operation is too large for the destination " - "media or file system.") - SHFILE_TO_NET_ERROR(DE_SRC_IS_CDROM, 0x86, ACCESS_DENIED, - "The source is a read-only CD-ROM, possibly unformatted.") - SHFILE_TO_NET_ERROR(DE_SRC_IS_DVD, 0x87, ACCESS_DENIED, - "The source is a read-only DVD, possibly unformatted.") - SHFILE_TO_NET_ERROR(DE_SRC_IS_CDRECORD, 0x88, ACCESS_DENIED, - "The source is a writable CD-ROM, possibly unformatted.") - SHFILE_TO_NET_ERROR(DE_ERROR_MAX, 0xB7, FILE_PATH_TOO_LONG, - "MAX_PATH was exceeded during the operation.") - SHFILE_TO_NET_ERROR(XE_ERRORONDEST, 0x10000, UNEXPECTED, - "An unspecified error occurred on the destination.") - - // These are not expected to occur for in our usage. - SHFILE_TO_NET_ERROR(DE_MANYSRC1DEST, 0x72, FAILED, - "Multiple file paths were specified in the source buffer, " - "but only one destination file path.") - SHFILE_TO_NET_ERROR(DE_DIFFDIR, 0x73, FAILED, - "Rename operation was specified but the destination path is " - "a different directory. Use the move operation instead.") - SHFILE_TO_NET_ERROR(DE_ROOTDIR, 0x74, FAILED, - "The source is a root directory, which cannot be moved or renamed.") - SHFILE_TO_NET_ERROR(DE_DESTSUBTREE, 0x76, FAILED, - "The destination is a subtree of the source.") - SHFILE_TO_NET_ERROR(DE_MANYDEST, 0x7A, FAILED, - "The operation involved multiple destination paths, " - "which can fail in the case of a move operation.") - SHFILE_TO_NET_ERROR(DE_DESTSAMETREE, 0x7D, FAILED, - "The source and destination have the same parent folder.") - SHFILE_TO_NET_ERROR(DE_UNKNOWN_ERROR, 0x402, FAILED, - "An unknown error occurred. " - "This is typically due to an invalid path in the source or destination." - " This error does not occur on Windows Vista and later.") - SHFILE_TO_NET_ERROR(DE_ROOTDIR | ERRORONDEST, 0x10074, FAILED, - "Destination is a root directory and cannot be renamed.") - default: - break; - } - - // If not one of the above codes, it should be a standard Windows error code. - return static_cast<net::Error>(net::MapSystemError(code)); -} - -#undef SHFILE_TO_NET_ERROR - -// Renames a file using the SHFileOperation API to ensure that the target file -// gets the correct default security descriptor in the new path. -// Returns a network error, or net::OK for success. -net::Error RenameFileAndResetSecurityDescriptor( - const FilePath& source_file_path, - const FilePath& target_file_path) { - base::ThreadRestrictions::AssertIOAllowed(); - - // The parameters to SHFileOperation must be terminated with 2 NULL chars. - std::wstring source = source_file_path.value(); - std::wstring target = target_file_path.value(); - - source.append(1, L'\0'); - target.append(1, L'\0'); - - SHFILEOPSTRUCT move_info = {0}; - move_info.wFunc = FO_MOVE; - move_info.pFrom = source.c_str(); - move_info.pTo = target.c_str(); - move_info.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | - FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS; - - int result = SHFileOperation(&move_info); - - if (result == 0) - return (move_info.fAnyOperationsAborted) ? net::ERR_ABORTED : net::OK; - - return MapShFileOperationCodes(result); -} - -#endif - -} // namespace - // This will initialize the entire array to zero. const unsigned char BaseFile::kEmptySha256Hash[] = { 0 }; @@ -208,7 +30,7 @@ BaseFile::BaseFile(const FilePath& full_path, const GURL& referrer_url, int64 received_bytes, bool calculate_hash, - const std::string& hash_state, + const std::string& hash_state_bytes, scoped_ptr<net::FileStream> file_stream, const net::BoundNetLog& bound_net_log) : full_path_(full_path), @@ -224,9 +46,10 @@ BaseFile::BaseFile(const FilePath& full_path, if (calculate_hash_) { secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256)); if ((bytes_so_far_ > 0) && // Not starting at the beginning. - (hash_state != "") && // Reasonably sure we have a hash state. - (!IsEmptyHash(hash_state))) { - SetHashState(hash_state); + (!IsEmptyHash(hash_state_bytes))) { + Pickle hash_state(hash_state_bytes.c_str(), hash_state_bytes.size()); + PickleIterator data_iterator(hash_state); + secure_hash_->Deserialize(&data_iterator); } } } @@ -239,7 +62,8 @@ BaseFile::~BaseFile() { Cancel(); // Will delete the file. } -net::Error BaseFile::Initialize(const FilePath& default_directory) { +content::DownloadInterruptReason BaseFile::Initialize( + const FilePath& default_directory) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(!detached_); @@ -260,7 +84,8 @@ net::Error BaseFile::Initialize(const FilePath& default_directory) { if ((initial_directory.empty() || !file_util::CreateTemporaryFileInDir(initial_directory, &temp_file)) && !file_util::CreateTemporaryFile(&temp_file)) { - return LOG_ERROR("unable to create", net::ERR_FILE_NOT_FOUND); + return LogInterruptReason("Unable to create", 0, + content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); } full_path_ = temp_file; } @@ -268,7 +93,8 @@ net::Error BaseFile::Initialize(const FilePath& default_directory) { return Open(); } -net::Error BaseFile::AppendDataToFile(const char* data, size_t data_len) { +content::DownloadInterruptReason BaseFile::AppendDataToFile(const char* data, + size_t data_len) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(!detached_); @@ -280,11 +106,12 @@ net::Error BaseFile::AppendDataToFile(const char* data, size_t data_len) { } if (!file_stream_.get()) - return LOG_ERROR("get", net::ERR_INVALID_HANDLE); + return LogInterruptReason("No file stream on append", 0, + content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); // TODO(phajdan.jr): get rid of this check. if (data_len == 0) - return net::OK; + return content::DOWNLOAD_INTERRUPT_REASON_NONE; // The Write call below is not guaranteed to write all the data. size_t write_count = 0; @@ -303,7 +130,7 @@ net::Error BaseFile::AppendDataToFile(const char* data, size_t data_len) { // Report errors on file writes. if (write_result < 0) - return LOG_ERROR("Write", write_result); + return LogNetError("Write", static_cast<net::Error>(write_result)); } // Update status. @@ -320,84 +147,43 @@ net::Error BaseFile::AppendDataToFile(const char* data, size_t data_len) { if (calculate_hash_) secure_hash_->Update(data, data_len); - return net::OK; + return content::DOWNLOAD_INTERRUPT_REASON_NONE; } -net::Error BaseFile::Rename(const FilePath& new_path) { +content::DownloadInterruptReason BaseFile::Rename(const FilePath& new_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + content::DownloadInterruptReason rename_result = + content::DOWNLOAD_INTERRUPT_REASON_NONE; + + // If the new path is same as the old one, there is no need to perform the + // following renaming logic. + if (new_path == full_path_) + return content::DOWNLOAD_INTERRUPT_REASON_NONE; // Save the information whether the download is in progress because // it will be overwritten by closing the file. - bool saved_in_progress = in_progress(); + bool was_in_progress = in_progress(); - bound_net_log_.AddEvent( + bound_net_log_.BeginEvent( net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED, base::Bind(&download_net_logs::FileRenamedCallback, &full_path_, &new_path)); - - // If the new path is same as the old one, there is no need to perform the - // following renaming logic. - if (new_path == full_path_) { - // Don't close the file if we're not done (finished or canceled). - if (!saved_in_progress) - Close(); - - return net::OK; - } - Close(); - file_util::CreateDirectory(new_path.DirName()); -#if defined(OS_WIN) - // We cannot rename because rename will keep the same security descriptor - // on the destination file. We want to recreate the security descriptor - // with the security that makes sense in the new path. - // |RenameFileAndResetSecurityDescriptor| returns a windows-specific - // error, whch we must translate into a net::Error. - net::Error rename_err = - RenameFileAndResetSecurityDescriptor(full_path_, new_path); - if (rename_err != net::OK) - return LOG_ERROR("RenameFileAndResetSecurityDescriptor", rename_err); -#elif defined(OS_POSIX) - { - // Similarly, on Unix, we're moving a temp file created with permissions - // 600 to |new_path|. Here, we try to fix up the destination file with - // appropriate permissions. - struct stat st; - // First check the file existence and create an empty file if it doesn't - // exist. - if (!file_util::PathExists(new_path)) { - int write_error = file_util::WriteFile(new_path, "", 0); - if (write_error < 0) - return LOG_ERROR("WriteFile", net::MapSystemError(errno)); - } - int stat_error = stat(new_path.value().c_str(), &st); - bool stat_succeeded = (stat_error == 0); - if (!stat_succeeded) - LOG_ERROR("stat", net::MapSystemError(errno)); - - // TODO(estade): Move() falls back to copying and deleting when a simple - // rename fails. Copying sucks for large downloads. crbug.com/8737 - if (!file_util::Move(full_path_, new_path)) - return LOG_ERROR("Move", net::MapSystemError(errno)); - - if (stat_succeeded) { - // On Windows file systems (FAT, NTFS), chmod fails. This is OK. - int chmod_error = chmod(new_path.value().c_str(), st.st_mode); - if (chmod_error < 0) - LOG_ERROR("chmod", net::MapSystemError(errno)); - } - } -#endif - - full_path_ = new_path; + // A simple rename wouldn't work here since we want the file to have + // permissions / security descriptors that makes sense in the new directory. + rename_result = MoveFileAndAdjustPermissions(new_path); - // We don't need to re-open the file if we're done (finished or canceled). - if (!saved_in_progress) - return net::OK; + if (rename_result == content::DOWNLOAD_INTERRUPT_REASON_NONE) { + full_path_ = new_path; + // Re-open the file if we were still using it. + if (was_in_progress) + rename_result = Open(); + } - return Open(); + bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED); + return rename_result; } void BaseFile::Detach() { @@ -429,6 +215,17 @@ void BaseFile::Finish() { Close(); } +// OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations. +#if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX) +void BaseFile::AnnotateWithSourceInformation() { +} +#endif + +int64 BaseFile::CurrentSpeed() const { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + return CurrentSpeedAtTime(base::TimeTicks::Now()); +} + bool BaseFile::GetHash(std::string* hash) { DCHECK(!detached_); hash->assign(reinterpret_cast<const char*>(sha256_hash_), @@ -448,36 +245,21 @@ std::string BaseFile::GetHashState() { hash_state.size()); } -bool BaseFile::SetHashState(const std::string& hash_state_bytes) { - if (!calculate_hash_) - return false; - - Pickle hash_state(hash_state_bytes.c_str(), hash_state_bytes.size()); - PickleIterator data_iterator(hash_state); - - return secure_hash_->Deserialize(&data_iterator); -} - +// static bool BaseFile::IsEmptyHash(const std::string& hash) { return (hash.size() == kSha256HashLen && 0 == memcmp(hash.data(), kEmptySha256Hash, sizeof(kSha256HashLen))); } -void BaseFile::AnnotateWithSourceInformation() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - DCHECK(!detached_); - -#if defined(OS_WIN) - // Sets the Zone to tell Windows that this file comes from the internet. - // We ignore the return value because a failure is not fatal. - win_util::SetInternetZoneIdentifier(full_path_, - UTF8ToWide(source_url_.spec())); -#elif defined(OS_MACOSX) - content::AddQuarantineMetadataToFile(full_path_, source_url_, referrer_url_); - content::AddOriginMetadataToFile(full_path_, source_url_, referrer_url_); -#elif defined(OS_LINUX) - content::AddOriginMetadataToFile(full_path_, source_url_, referrer_url_); -#endif +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'); } void BaseFile::CreateFileStream() { @@ -485,7 +267,7 @@ void BaseFile::CreateFileStream() { file_stream_->SetBoundNetLogSource(bound_net_log_); } -net::Error BaseFile::Open() { +content::DownloadInterruptReason BaseFile::Open() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(!detached_); DCHECK(!full_path_.empty()); @@ -502,19 +284,23 @@ net::Error BaseFile::Open() { int open_result = file_stream_->OpenSync( full_path_, base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_WRITE); - if (open_result != net::OK) - return ClearStream(LOG_ERROR("Open", open_result)); + if (open_result != net::OK) { + ClearStream(); + return LogNetError("Open", static_cast<net::Error>(open_result)); + } // We may be re-opening the file after rename. Always make sure we're // writing at the end of the file. int64 seek_result = file_stream_->SeekSync(net::FROM_END, 0); - if (seek_result < 0) - return ClearStream(LOG_ERROR("Seek", seek_result)); + if (seek_result < 0) { + ClearStream(); + return LogNetError("Seek", static_cast<net::Error>(seek_result)); + } } else { file_stream_->SetBoundNetLogSource(bound_net_log_); } - return net::OK; + return content::DOWNLOAD_INTERRUPT_REASON_NONE; } void BaseFile::Close() { @@ -529,27 +315,15 @@ void BaseFile::Close() { file_stream_->FlushSync(); #endif file_stream_->CloseSync(); - ClearStream(net::OK); + ClearStream(); } } -net::Error BaseFile::ClearStream(net::Error net_error) { +void BaseFile::ClearStream() { // This should only be called when we have a stream. DCHECK(file_stream_.get() != NULL); file_stream_.reset(); bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED); - return net_error; -} - -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'); } int64 BaseFile::CurrentSpeedAtTime(base::TimeTicks current_time) const { @@ -558,7 +332,34 @@ int64 BaseFile::CurrentSpeedAtTime(base::TimeTicks current_time) const { return diff_ms == 0 ? 0 : bytes_so_far() * 1000 / diff_ms; } -int64 BaseFile::CurrentSpeed() const { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); - return CurrentSpeedAtTime(base::TimeTicks::Now()); +content::DownloadInterruptReason BaseFile::LogNetError( + const char* operation, + net::Error error) { + bound_net_log_.AddEvent( + net::NetLog::TYPE_DOWNLOAD_FILE_ERROR, + base::Bind(&download_net_logs::FileErrorCallback, operation, error)); + return content::ConvertNetErrorToInterruptReason( + error, content::DOWNLOAD_INTERRUPT_FROM_DISK); +} + +content::DownloadInterruptReason BaseFile::LogSystemError( + const char* operation, + int os_error) { + // There's no direct conversion from a system error to an interrupt reason. + net::Error net_error = net::MapSystemError(os_error); + return LogInterruptReason( + operation, os_error, + content::ConvertNetErrorToInterruptReason( + net_error, content::DOWNLOAD_INTERRUPT_FROM_DISK)); +} + +content::DownloadInterruptReason BaseFile::LogInterruptReason( + const char* operation, + int os_error, + content::DownloadInterruptReason reason) { + bound_net_log_.AddEvent( + net::NetLog::TYPE_DOWNLOAD_FILE_ERROR, + base::Bind(&download_net_logs::FileInterruptedCallback, operation, + os_error, reason)); + return reason; } diff --git a/content/browser/download/base_file.h b/content/browser/download/base_file.h index d1daaf1..e6ae0a3 100644 --- a/content/browser/download/base_file.h +++ b/content/browser/download/base_file.h @@ -13,11 +13,15 @@ #include "base/memory/scoped_ptr.h" #include "base/time.h" #include "content/common/content_export.h" +#include "content/public/browser/download_interrupt_reasons.h" #include "googleurl/src/gurl.h" #include "net/base/file_stream.h" #include "net/base/net_errors.h" #include "net/base/net_log.h" +namespace content { +enum DownloadInterruptReason; +} namespace crypto { class SecureHash; } @@ -41,21 +45,23 @@ class CONTENT_EXPORT BaseFile { const net::BoundNetLog& bound_net_log); virtual ~BaseFile(); - // Returns net::OK on success, or a network error code 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. - net::Error Initialize(const FilePath& default_directory); + // 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. + content::DownloadInterruptReason Initialize( + const FilePath& default_directory); - // Write a new chunk of data to the file. - // Returns net::OK on success (all bytes written to the file), - // or a network error code on failure. - net::Error AppendDataToFile(const char* data, size_t data_len); + // Write a new chunk of data to the file. Returns a DownloadInterruptReason + // indicating the result of the operation. + content::DownloadInterruptReason AppendDataToFile(const char* data, + size_t data_len); - // Rename the download file. - // Returns net::OK on success, or a network error code on failure. - virtual net::Error Rename(const FilePath& full_path); + // Rename the download file. Returns a DownloadInterruptReason indicating the + // result of the operation. + virtual content::DownloadInterruptReason Rename(const FilePath& full_path); // Detach the file so it is not deleted on destruction. virtual void Detach(); @@ -90,30 +96,53 @@ class CONTENT_EXPORT BaseFile { virtual std::string DebugString() const; - protected: - virtual void CreateFileStream(); // For testing. - // Returns net::OK on success, or a network error code on failure. - net::Error Open(); - void Close(); - - // Full path to the file including the file name. - FilePath full_path_; - private: friend class BaseFileTest; FRIEND_TEST_ALL_PREFIXES(BaseFileTest, IsEmptyHash); + // Re-initializes file_stream_ with a newly allocated net::FileStream(). + void CreateFileStream(); + + // Creates and opens the file_stream_ if it is NULL. + content::DownloadInterruptReason Open(); + + // Closes and resets file_stream_. + void Close(); + + // Resets file_stream_. + void ClearStream(); + + // Platform specific method that moves a file to a new path and adjusts the + // security descriptor / permissions on the file to match the defaults for the + // new directory. + content::DownloadInterruptReason MoveFileAndAdjustPermissions( + const FilePath& new_path); + // Split out from CurrentSpeed to enable testing. int64 CurrentSpeedAtTime(base::TimeTicks current_time) const; - // Resets the current state of the hash to the contents of |hash_state_bytes|. - virtual bool SetHashState(const std::string& hash_state_bytes); + // Log a TYPE_DOWNLOAD_FILE_ERROR NetLog event with |error| and passes error + // on through, converting to a |content::DownloadInterruptReason|. + content::DownloadInterruptReason LogNetError( + const char* operation, net::Error error); - net::Error ClearStream(net::Error error); + // Log the system error in |os_error| and converts it into a + // |content::DownloadInterruptReason|. + content::DownloadInterruptReason LogSystemError( + const char* operation, int os_error); + + // Log a TYPE_DOWNLOAD_FILE_ERROR NetLog event with |os_error| and |reason|. + // Returns |reason|. + content::DownloadInterruptReason LogInterruptReason( + const char* operation, int os_error, + content::DownloadInterruptReason reason); static const size_t kSha256HashLen = 32; static const unsigned char kEmptySha256Hash[kSha256HashLen]; + // Full path to the file including the file name. + FilePath full_path_; + // Source URL for the file being downloaded. GURL source_url_; diff --git a/content/browser/download/base_file_linux.cc b/content/browser/download/base_file_linux.cc new file mode 100644 index 0000000..7f8b717 --- /dev/null +++ b/content/browser/download/base_file_linux.cc @@ -0,0 +1,15 @@ +// 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. + +#include "content/browser/download/base_file.h" + +#include "content/browser/download/file_metadata_linux.h" +#include "content/public/browser/browser_thread.h" + +void BaseFile::AnnotateWithSourceInformation() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); + DCHECK(!detached_); + + content::AddOriginMetadataToFile(full_path_, source_url_, referrer_url_); +} diff --git a/content/browser/download/base_file_mac.cc b/content/browser/download/base_file_mac.cc new file mode 100644 index 0000000..88ad5f0 --- /dev/null +++ b/content/browser/download/base_file_mac.cc @@ -0,0 +1,16 @@ +// 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. + +#include "content/browser/download/base_file.h" + +#include "content/browser/download/file_metadata_mac.h" +#include "content/public/browser/browser_thread.h" + +void BaseFile::AnnotateWithSourceInformation() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); + DCHECK(!detached_); + + content::AddQuarantineMetadataToFile(full_path_, source_url_, referrer_url_); + content::AddOriginMetadataToFile(full_path_, source_url_, referrer_url_); +} diff --git a/content/browser/download/base_file_posix.cc b/content/browser/download/base_file_posix.cc new file mode 100644 index 0000000..b5538bf --- /dev/null +++ b/content/browser/download/base_file_posix.cc @@ -0,0 +1,40 @@ +// 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. + +#include "content/browser/download/base_file.h" + +#include "base/file_util.h" +#include "content/public/browser/download_interrupt_reasons.h" + +content::DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions( + const FilePath& new_path) { + // Similarly, on Unix, we're moving a temp file created with permissions 600 + // to |new_path|. Here, we try to fix up the destination file with appropriate + // permissions. + struct stat st; + // First check the file existence and create an empty file if it doesn't + // exist. + if (!file_util::PathExists(new_path)) { + int write_error = file_util::WriteFile(new_path, "", 0); + if (write_error < 0) + return LogSystemError("WriteFile", errno); + } + int stat_error = stat(new_path.value().c_str(), &st); + bool stat_succeeded = (stat_error == 0); + if (!stat_succeeded) + LogSystemError("stat", errno); + + // TODO(estade): Move() falls back to copying and deleting when a simple + // rename fails. Copying sucks for large downloads. crbug.com/8737 + if (!file_util::Move(full_path_, new_path)) + return LogSystemError("Move", errno); + + if (stat_succeeded) { + // On Windows file systems (FAT, NTFS), chmod fails. This is OK. + int chmod_error = chmod(new_path.value().c_str(), st.st_mode); + if (chmod_error < 0) + LogSystemError("chmod", errno); + } + return content::DOWNLOAD_INTERRUPT_REASON_NONE; +} diff --git a/content/browser/download/base_file_unittest.cc b/content/browser/download/base_file_unittest.cc index 38206e5..e672fbb 100644 --- a/content/browser/download/base_file_unittest.cc +++ b/content/browser/download/base_file_unittest.cc @@ -11,10 +11,10 @@ #include "base/string_number_conversions.h" #include "base/test/test_file_util.h" #include "content/browser/browser_thread_impl.h" +#include "content/public/browser/download_interrupt_reasons.h" #include "crypto/secure_hash.h" #include "net/base/file_stream.h" #include "net/base/mock_file_stream.h" -#include "net/base/net_errors.h" #include "testing/gtest/include/gtest/gtest.h" using content::BrowserThread; @@ -44,7 +44,7 @@ class BaseFileTest : public testing::Test { BaseFileTest() : expect_file_survives_(false), expect_in_progress_(true), - expected_error_(false), + expected_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE), file_thread_(BrowserThread::FILE, &message_loop_) { } @@ -112,20 +112,29 @@ class BaseFileTest : public testing::Test { net::BoundNetLog())); } - int AppendDataToFile(const std::string& data) { + bool InitializeFile() { + content::DownloadInterruptReason result = + base_file_->Initialize(temp_dir_.path()); + EXPECT_EQ(expected_error_, result); + return result == content::DOWNLOAD_INTERRUPT_REASON_NONE; + } + + bool AppendDataToFile(const std::string& data) { EXPECT_EQ(expect_in_progress_, base_file_->in_progress()); - int appended = base_file_->AppendDataToFile(data.data(), data.size()); - if (appended == net::OK) - EXPECT_TRUE(expect_in_progress_) - << " appended = " << appended; + content::DownloadInterruptReason result = + base_file_->AppendDataToFile(data.data(), data.size()); + if (result == content::DOWNLOAD_INTERRUPT_REASON_NONE) + EXPECT_TRUE(expect_in_progress_) << " result = " << result; + + EXPECT_EQ(expected_error_, result); if (base_file_->in_progress()) { expected_data_ += data; - if (!expected_error_) { + if (expected_error_ == content::DOWNLOAD_INTERRUPT_REASON_NONE) { EXPECT_EQ(static_cast<int64>(expected_data_.size()), base_file_->bytes_so_far()); } } - return appended; + return result == content::DOWNLOAD_INTERRUPT_REASON_NONE; } void set_expected_data(const std::string& data) { expected_data_ = data; } @@ -143,11 +152,13 @@ class BaseFileTest : public testing::Test { scoped_ptr<net::FileStream>(), net::BoundNetLog()); - EXPECT_EQ(net::OK, file.Initialize(temp_dir_.path())); + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, + file.Initialize(temp_dir_.path())); file_name = file.full_path(); EXPECT_NE(FilePath::StringType(), file_name.value()); - EXPECT_EQ(net::OK, file.AppendDataToFile(kTestData4, kTestDataLength4)); + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, + file.AppendDataToFile(kTestData4, kTestDataLength4)); // Keep the file from getting deleted when existing_file_name is deleted. file.Detach(); @@ -166,7 +177,8 @@ class BaseFileTest : public testing::Test { "", scoped_ptr<net::FileStream>(), net::BoundNetLog()); - EXPECT_EQ(net::OK, duplicate_file.Initialize(temp_dir_.path())); + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, + duplicate_file.Initialize(temp_dir_.path())); // Write something into it. duplicate_file.AppendDataToFile(kTestData4, kTestDataLength4); // Detach the file so it isn't deleted on destruction of |duplicate_file|. @@ -183,7 +195,7 @@ class BaseFileTest : public testing::Test { return base_file_->start_tick_; } - void set_expected_error(net::Error err) { + void set_expected_error(content::DownloadInterruptReason err) { expected_error_ = err; } @@ -210,7 +222,7 @@ class BaseFileTest : public testing::Test { private: // Keep track of what data should be saved to the disk file. std::string expected_data_; - bool expected_error_; + content::DownloadInterruptReason expected_error_; // Mock file thread to satisfy debug checks in BaseFile. MessageLoop message_loop_; @@ -229,7 +241,7 @@ TEST_F(BaseFileTest, CreateDestroy) { // Cancel the download explicitly. TEST_F(BaseFileTest, Cancel) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); EXPECT_TRUE(file_util::PathExists(base_file_->full_path())); base_file_->Cancel(); EXPECT_FALSE(file_util::PathExists(base_file_->full_path())); @@ -239,8 +251,8 @@ TEST_F(BaseFileTest, Cancel) { // Write data to the file and detach it, so it doesn't get deleted // automatically when base_file_ is destructed. TEST_F(BaseFileTest, WriteAndDetach) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); + ASSERT_TRUE(InitializeFile()); + ASSERT_TRUE(AppendDataToFile(kTestData1)); base_file_->Finish(); base_file_->Detach(); expect_file_survives_ = true; @@ -256,8 +268,8 @@ TEST_F(BaseFileTest, WriteWithHashAndDetach) { base::HexEncode(expected_hash.data(), expected_hash.size()); MakeFileWithHash(); - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); + ASSERT_TRUE(InitializeFile()); + ASSERT_TRUE(AppendDataToFile(kTestData1)); base_file_->Finish(); std::string hash; @@ -272,16 +284,17 @@ TEST_F(BaseFileTest, WriteWithHashAndDetach) { // Rename the file after writing to it, then detach. TEST_F(BaseFileTest, WriteThenRenameAndDetach) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); FilePath initial_path(base_file_->full_path()); EXPECT_TRUE(file_util::PathExists(initial_path)); FilePath new_path(temp_dir_.path().AppendASCII("NewFile")); EXPECT_FALSE(file_util::PathExists(new_path)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); + ASSERT_TRUE(AppendDataToFile(kTestData1)); - EXPECT_EQ(net::OK, base_file_->Rename(new_path)); + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, + base_file_->Rename(new_path)); EXPECT_FALSE(file_util::PathExists(initial_path)); EXPECT_TRUE(file_util::PathExists(new_path)); @@ -292,17 +305,17 @@ TEST_F(BaseFileTest, WriteThenRenameAndDetach) { // Write data to the file once. TEST_F(BaseFileTest, SingleWrite) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); + ASSERT_TRUE(InitializeFile()); + ASSERT_TRUE(AppendDataToFile(kTestData1)); base_file_->Finish(); } // Write data to the file multiple times. TEST_F(BaseFileTest, MultipleWrites) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData2)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData3)); + ASSERT_TRUE(InitializeFile()); + ASSERT_TRUE(AppendDataToFile(kTestData1)); + ASSERT_TRUE(AppendDataToFile(kTestData2)); + ASSERT_TRUE(AppendDataToFile(kTestData3)); std::string hash; EXPECT_FALSE(base_file_->GetHash(&hash)); base_file_->Finish(); @@ -318,10 +331,10 @@ TEST_F(BaseFileTest, SingleWriteWithHash) { base::HexEncode(expected_hash.data(), expected_hash.size()); MakeFileWithHash(); - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); // Can get partial hash states before Finish() is called. EXPECT_STRNE(std::string().c_str(), base_file_->GetHashState().c_str()); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); + ASSERT_TRUE(AppendDataToFile(kTestData1)); EXPECT_STRNE(std::string().c_str(), base_file_->GetHashState().c_str()); base_file_->Finish(); @@ -343,10 +356,10 @@ TEST_F(BaseFileTest, MultipleWritesWithHash) { std::string hash; MakeFileWithHash(); - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData2)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData3)); + 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(); @@ -370,10 +383,10 @@ TEST_F(BaseFileTest, MultipleWritesInterruptedWithHash) { base::HexEncode(expected_hash.data(), expected_hash.size()); MakeFileWithHash(); - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); // Write some data - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData2)); + ASSERT_TRUE(AppendDataToFile(kTestData1)); + ASSERT_TRUE(AppendDataToFile(kTestData2)); // Get the hash state and file name. std::string hash_state; hash_state = base_file_->GetHashState(); @@ -389,9 +402,11 @@ TEST_F(BaseFileTest, MultipleWritesInterruptedWithHash) { hash_state, scoped_ptr<net::FileStream>(), net::BoundNetLog()); - ASSERT_EQ(net::OK, second_file.Initialize(temp_dir_.path())); + ASSERT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, + second_file.Initialize(temp_dir_.path())); std::string data(kTestData3); - EXPECT_EQ(net::OK, second_file.AppendDataToFile(data.data(), data.size())); + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, + second_file.AppendDataToFile(data.data(), data.size())); second_file.Finish(); std::string hash; @@ -403,16 +418,17 @@ TEST_F(BaseFileTest, MultipleWritesInterruptedWithHash) { // Rename the file after all writes to it. TEST_F(BaseFileTest, WriteThenRename) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); FilePath initial_path(base_file_->full_path()); EXPECT_TRUE(file_util::PathExists(initial_path)); FilePath new_path(temp_dir_.path().AppendASCII("NewFile")); EXPECT_FALSE(file_util::PathExists(new_path)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); + ASSERT_TRUE(AppendDataToFile(kTestData1)); - EXPECT_EQ(net::OK, base_file_->Rename(new_path)); + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, + base_file_->Rename(new_path)); EXPECT_FALSE(file_util::PathExists(initial_path)); EXPECT_TRUE(file_util::PathExists(new_path)); @@ -421,28 +437,29 @@ TEST_F(BaseFileTest, WriteThenRename) { // Rename the file while the download is still in progress. TEST_F(BaseFileTest, RenameWhileInProgress) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); FilePath initial_path(base_file_->full_path()); EXPECT_TRUE(file_util::PathExists(initial_path)); FilePath new_path(temp_dir_.path().AppendASCII("NewFile")); EXPECT_FALSE(file_util::PathExists(new_path)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); + ASSERT_TRUE(AppendDataToFile(kTestData1)); EXPECT_TRUE(base_file_->in_progress()); - EXPECT_EQ(net::OK, base_file_->Rename(new_path)); + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, + base_file_->Rename(new_path)); EXPECT_FALSE(file_util::PathExists(initial_path)); EXPECT_TRUE(file_util::PathExists(new_path)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData2)); + ASSERT_TRUE(AppendDataToFile(kTestData2)); base_file_->Finish(); } // Test that a failed rename reports the correct error. TEST_F(BaseFileTest, RenameWithError) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); // TestDir is a subdirectory in |temp_dir_| that we will make read-only so // that the rename will fail. @@ -455,7 +472,8 @@ TEST_F(BaseFileTest, RenameWithError) { { file_util::PermissionRestorer restore_permissions_for(test_dir); ASSERT_TRUE(file_util::MakeFileUnwritable(test_dir)); - EXPECT_EQ(net::ERR_ACCESS_DENIED, base_file_->Rename(new_path)); + EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED, + base_file_->Rename(new_path)); } base_file_->Finish(); @@ -488,12 +506,12 @@ TEST_F(BaseFileTest, MultipleWritesWithError) { "", mock_file_stream_scoped_ptr.Pass(), net::BoundNetLog())); - EXPECT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData2)); + ASSERT_TRUE(InitializeFile()); + ASSERT_TRUE(AppendDataToFile(kTestData1)); + ASSERT_TRUE(AppendDataToFile(kTestData2)); mock_file_stream->set_forced_error(net::ERR_ACCESS_DENIED); - set_expected_error(net::ERR_ACCESS_DENIED); - ASSERT_NE(net::OK, AppendDataToFile(kTestData3)); + set_expected_error(content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED); + ASSERT_FALSE(AppendDataToFile(kTestData3)); std::string hash; EXPECT_FALSE(base_file_->GetHash(&hash)); base_file_->Finish(); @@ -502,19 +520,20 @@ TEST_F(BaseFileTest, MultipleWritesWithError) { // Try to write to uninitialized file. TEST_F(BaseFileTest, UninitializedFile) { expect_in_progress_ = false; - EXPECT_EQ(net::ERR_INVALID_HANDLE, AppendDataToFile(kTestData1)); + set_expected_error(content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); + EXPECT_FALSE(AppendDataToFile(kTestData1)); } // Create two |BaseFile|s with the same file, and attempt to write to both. // Overwrite base_file_ with another file with the same name and // non-zero contents, and make sure the last file to close 'wins'. TEST_F(BaseFileTest, DuplicateBaseFile) { - EXPECT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); // Create another |BaseFile| referring to the file that |base_file_| owns. CreateFileWithName(base_file_->full_path()); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); + ASSERT_TRUE(AppendDataToFile(kTestData1)); base_file_->Finish(); } @@ -535,13 +554,13 @@ TEST_F(BaseFileTest, AppendToBaseFile) { scoped_ptr<net::FileStream>(), net::BoundNetLog())); - EXPECT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); const FilePath file_name = base_file_->full_path(); EXPECT_NE(FilePath::StringType(), file_name.value()); // Write into the file. - EXPECT_EQ(net::OK, AppendDataToFile(kTestData1)); + EXPECT_TRUE(AppendDataToFile(kTestData1)); base_file_->Finish(); base_file_->Detach(); @@ -570,16 +589,15 @@ TEST_F(BaseFileTest, ReadonlyBaseFile) { net::BoundNetLog())); expect_in_progress_ = false; - - int init_error = base_file_->Initialize(temp_dir_.path()); - DVLOG(1) << " init_error = " << init_error; - EXPECT_NE(net::OK, init_error); + set_expected_error(content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED); + EXPECT_FALSE(InitializeFile()); const FilePath file_name = base_file_->full_path(); EXPECT_NE(FilePath::StringType(), file_name.value()); // Write into the file. - EXPECT_NE(net::OK, AppendDataToFile(kTestData1)); + set_expected_error(content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); + EXPECT_FALSE(AppendDataToFile(kTestData1)); base_file_->Finish(); base_file_->Detach(); @@ -596,7 +614,7 @@ TEST_F(BaseFileTest, IsEmptyHash) { // Test that calculating speed after no writes. TEST_F(BaseFileTest, SpeedWithoutWrite) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); base::TimeTicks current = StartTick() + kElapsedTimeDelta; ASSERT_EQ(0, CurrentSpeedAtTime(current)); base_file_->Finish(); @@ -604,8 +622,8 @@ TEST_F(BaseFileTest, SpeedWithoutWrite) { // Test that calculating speed after a single write. TEST_F(BaseFileTest, SpeedAfterSingleWrite) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); + ASSERT_TRUE(InitializeFile()); + ASSERT_TRUE(AppendDataToFile(kTestData1)); base::TimeTicks current = StartTick() + kElapsedTimeDelta; int64 expected_speed = kTestDataLength1 / kElapsedTimeSeconds; ASSERT_EQ(expected_speed, CurrentSpeedAtTime(current)); @@ -614,11 +632,11 @@ TEST_F(BaseFileTest, SpeedAfterSingleWrite) { // Test that calculating speed after a multiple writes. TEST_F(BaseFileTest, SpeedAfterMultipleWrite) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData2)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData3)); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData4)); + ASSERT_TRUE(InitializeFile()); + ASSERT_TRUE(AppendDataToFile(kTestData1)); + ASSERT_TRUE(AppendDataToFile(kTestData2)); + ASSERT_TRUE(AppendDataToFile(kTestData3)); + ASSERT_TRUE(AppendDataToFile(kTestData4)); base::TimeTicks current = StartTick() + kElapsedTimeDelta; int64 expected_speed = (kTestDataLength1 + kTestDataLength2 + kTestDataLength3 + kTestDataLength4) / kElapsedTimeSeconds; @@ -628,8 +646,8 @@ TEST_F(BaseFileTest, SpeedAfterMultipleWrite) { // Test that calculating speed after no delay - should not divide by 0. TEST_F(BaseFileTest, SpeedAfterNoElapsedTime) { - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); - ASSERT_EQ(net::OK, AppendDataToFile(kTestData1)); + ASSERT_TRUE(InitializeFile()); + ASSERT_TRUE(AppendDataToFile(kTestData1)); ASSERT_EQ(0, CurrentSpeedAtTime(StartTick())); base_file_->Finish(); } @@ -637,7 +655,7 @@ TEST_F(BaseFileTest, SpeedAfterNoElapsedTime) { // Test that a temporary file is created in the default download directory. TEST_F(BaseFileTest, CreatedInDefaultDirectory) { ASSERT_TRUE(base_file_->full_path().empty()); - ASSERT_EQ(net::OK, base_file_->Initialize(temp_dir_.path())); + ASSERT_TRUE(InitializeFile()); EXPECT_FALSE(base_file_->full_path().empty()); // On Windows, CreateTemporaryFileInDir() will cause a path with short names diff --git a/content/browser/download/base_file_win.cc b/content/browser/download/base_file_win.cc new file mode 100644 index 0000000..b3b7e18 --- /dev/null +++ b/content/browser/download/base_file_win.cc @@ -0,0 +1,189 @@ +// 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. + +#include "content/browser/download/base_file.h" + +#include <windows.h> +#include <shellapi.h> + +#include "base/threading/thread_restrictions.h" +#include "base/utf_string_conversions.h" +#include "content/browser/download/download_interrupt_reasons_impl.h" +#include "content/browser/safe_util_win.h" +#include "content/public/browser/browser_thread.h" + +namespace { + +// Maps the result of a call to |SHFileOperation()| onto a +// |content::DownloadInterruptReason|. +// +// These return codes are *old* (as in, DOS era), and specific to +// |SHFileOperation()|. +// They do not appear in any windows header. +// +// See http://msdn.microsoft.com/en-us/library/bb762164(VS.85).aspx. +content::DownloadInterruptReason MapShFileOperationCodes(int code) { + // Check these pre-Win32 error codes first, then check for matches + // in Winerror.h. + + switch (code) { + // The source and destination files are the same file. + // DE_SAMEFILE == 0x71 + case 0x71: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // The operation was canceled by the user, or silently canceled if the + // appropriate flags were supplied to SHFileOperation. + // DE_OPCANCELLED == 0x75 + case 0x75: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // Security settings denied access to the source. + // DE_ACCESSDENIEDSRC == 0x78 + case 0x78: return content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED; + + // The source or destination path exceeded or would exceed MAX_PATH. + // DE_PATHTOODEEP == 0x79 + case 0x79: return content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG; + + // The path in the source or destination or both was invalid. + // DE_INVALIDFILES == 0x7C + case 0x7C: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // The destination path is an existing file. + // DE_FLDDESTISFILE == 0x7E + case 0x7E: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // The destination path is an existing folder. + // DE_FILEDESTISFLD == 0x80 + case 0x80: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // The name of the file exceeds MAX_PATH. + // DE_FILENAMETOOLONG == 0x81 + case 0x81: return content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG; + + // The destination is a read-only CD-ROM, possibly unformatted. + // DE_DEST_IS_CDROM == 0x82 + case 0x82: return content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED; + + // The destination is a read-only DVD, possibly unformatted. + // DE_DEST_IS_DVD == 0x83 + case 0x83: return content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED; + + // The destination is a writable CD-ROM, possibly unformatted. + // DE_DEST_IS_CDRECORD == 0x84 + case 0x84: return content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED; + + // The file involved in the operation is too large for the destination + // media or file system. + // DE_FILE_TOO_LARGE == 0x85 + case 0x85: return content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE; + + // The source is a read-only CD-ROM, possibly unformatted. + // DE_SRC_IS_CDROM == 0x86 + case 0x86: return content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED; + + // The source is a read-only DVD, possibly unformatted. + // DE_SRC_IS_DVD == 0x87 + case 0x87: return content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED; + + // The source is a writable CD-ROM, possibly unformatted. + // DE_SRC_IS_CDRECORD == 0x88 + case 0x88: return content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED; + + // MAX_PATH was exceeded during the operation. + // DE_ERROR_MAX == 0xB7 + case 0xB7: return content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG; + + // An unspecified error occurred on the destination. + // XE_ERRORONDEST == 0x10000 + case 0x10000: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // Multiple file paths were specified in the source buffer, but only one + // destination file path. + // DE_MANYSRC1DEST == 0x72 + case 0x72: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // Rename operation was specified but the destination path is + // a different directory. Use the move operation instead. + // DE_DIFFDIR == 0x73 + case 0x73: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // The source is a root directory, which cannot be moved or renamed. + // DE_ROOTDIR == 0x74 + case 0x74: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // The destination is a subtree of the source. + // DE_DESTSUBTREE == 0x76 + case 0x76: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // The operation involved multiple destination paths, + // which can fail in the case of a move operation. + // DE_MANYDEST == 0x7A + case 0x7A: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // The source and destination have the same parent folder. + // DE_DESTSAMETREE == 0x7D + case 0x7D: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // An unknown error occurred. This is typically due to an invalid path in + // the source or destination. This error does not occur on Windows Vista + // and later. + // DE_UNKNOWN_ERROR == 0x402 + case 0x402: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + + // Destination is a root directory and cannot be renamed. + // DE_ROOTDIR | ERRORONDEST == 0x10074 + case 0x10074: return content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + } + + // If not one of the above codes, it should be a standard Windows error code. + return content::ConvertNetErrorToInterruptReason( + net::MapSystemError(code), content::DOWNLOAD_INTERRUPT_FROM_DISK); +} + +} // namespace + +// Renames a file using the SHFileOperation API to ensure that the target file +// gets the correct default security descriptor in the new path. +// Returns a network error, or net::OK for success. +content::DownloadInterruptReason BaseFile::MoveFileAndAdjustPermissions( + const FilePath& new_path) { + base::ThreadRestrictions::AssertIOAllowed(); + + // The parameters to SHFileOperation must be terminated with 2 NULL chars. + FilePath::StringType source = full_path_.value(); + FilePath::StringType target = new_path.value(); + + source.append(1, L'\0'); + target.append(1, L'\0'); + + SHFILEOPSTRUCT move_info = {0}; + move_info.wFunc = FO_MOVE; + move_info.pFrom = source.c_str(); + move_info.pTo = target.c_str(); + move_info.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | + FOF_NOCONFIRMMKDIR | FOF_NOCOPYSECURITYATTRIBS; + + int result = SHFileOperation(&move_info); + content::DownloadInterruptReason interrupt_reason = + content::DOWNLOAD_INTERRUPT_REASON_NONE; + + if (result == 0 && move_info.fAnyOperationsAborted) + interrupt_reason = content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED; + else if (result != 0) + interrupt_reason = MapShFileOperationCodes(result); + + if (interrupt_reason != content::DOWNLOAD_INTERRUPT_REASON_NONE) + return LogInterruptReason("SHFileOperation", result, interrupt_reason); + return interrupt_reason; +} + +void BaseFile::AnnotateWithSourceInformation() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); + DCHECK(!detached_); + + // Sets the Zone to tell Windows that this file comes from the internet. + // We ignore the return value because a failure is not fatal. + win_util::SetInternetZoneIdentifier(full_path_, + UTF8ToWide(source_url_.spec())); +} diff --git a/content/browser/download/download_file_impl.cc b/content/browser/download/download_file_impl.cc index 1020816..c018599 100644 --- a/content/browser/download/download_file_impl.cc +++ b/content/browser/download/download_file_impl.cc @@ -66,12 +66,11 @@ void DownloadFileImpl::Initialize(const InitializeCallback& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); update_timer_.reset(new base::RepeatingTimer<DownloadFileImpl>()); - net::Error net_result = file_.Initialize(default_download_directory_); - if (net_result != net::OK) { + content::DownloadInterruptReason result = + file_.Initialize(default_download_directory_); + if (result != content::DOWNLOAD_INTERRUPT_REASON_NONE) { BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, base::Bind( - callback, content::ConvertNetErrorToInterruptReason( - net_result, content::DOWNLOAD_INTERRUPT_FROM_DISK))); + BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); return; } @@ -99,9 +98,7 @@ content::DownloadInterruptReason DownloadFileImpl::AppendDataToFile( base::TimeDelta::FromMilliseconds(kUpdatePeriodMs), this, &DownloadFileImpl::SendUpdate); } - return content::ConvertNetErrorToInterruptReason( - file_.AppendDataToFile(data, data_len), - content::DOWNLOAD_INTERRUPT_FROM_DISK); + return file_.AppendDataToFile(data, data_len); } void DownloadFileImpl::Rename(const FilePath& full_path, @@ -120,10 +117,8 @@ void DownloadFileImpl::Rename(const FilePath& full_path, } } - net::Error rename_error = file_.Rename(new_path); - content::DownloadInterruptReason reason( - content::DOWNLOAD_INTERRUPT_REASON_NONE); - if (net::OK != rename_error) { + content::DownloadInterruptReason reason = file_.Rename(new_path); + if (reason != content::DOWNLOAD_INTERRUPT_REASON_NONE) { // Make sure our information is updated, since we're about to // error out. SendUpdate(); @@ -131,10 +126,6 @@ void DownloadFileImpl::Rename(const FilePath& full_path, // Null out callback so that we don't do any more stream processing. stream_reader_->RegisterCallback(base::Closure()); - reason = - content::ConvertNetErrorToInterruptReason( - rename_error, - content::DOWNLOAD_INTERRUPT_FROM_DISK); new_path.clear(); } diff --git a/content/browser/download/download_net_log_parameters.cc b/content/browser/download/download_net_log_parameters.cc index 00a9d5d..c0d42cf 100644 --- a/content/browser/download/download_net_log_parameters.cc +++ b/content/browser/download/download_net_log_parameters.cc @@ -183,4 +183,19 @@ base::Value* FileErrorCallback(const char* operation, return dict; } +base::Value* FileInterruptedCallback(const char* operation, + int os_error, + content::DownloadInterruptReason reason, + net::NetLog::LogLevel /* log_level */) { + DictionaryValue* dict = new DictionaryValue(); + + dict->SetString("operation", operation); + if (os_error != 0) + dict->SetInteger("os_error", os_error); + dict->SetString("interrupt_reason", InterruptReasonDebugString(reason)); + + return dict; +} + + } // namespace download_net_logs diff --git a/content/browser/download/download_net_log_parameters.h b/content/browser/download/download_net_log_parameters.h index 5167772..1d94233 100644 --- a/content/browser/download/download_net_log_parameters.h +++ b/content/browser/download/download_net_log_parameters.h @@ -80,6 +80,11 @@ base::Value* FileErrorCallback(const char* operation, net::Error net_error, net::NetLog::LogLevel log_level); +base::Value* FileInterruptedCallback(const char* operation, + int os_error, + content::DownloadInterruptReason reason, + net::NetLog::LogLevel log_level); + } // namespace download_net_logs #endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_NET_LOG_PARAMETERS_H_ diff --git a/content/browser/download/save_file.cc b/content/browser/download/save_file.cc index cd0acba..91fe2ac 100644 --- a/content/browser/download/save_file.cc +++ b/content/browser/download/save_file.cc @@ -34,15 +34,16 @@ SaveFile::~SaveFile() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); } -net::Error SaveFile::Initialize() { +content::DownloadInterruptReason SaveFile::Initialize() { return file_.Initialize(FilePath()); } -net::Error SaveFile::AppendDataToFile(const char* data, size_t data_len) { +content::DownloadInterruptReason SaveFile::AppendDataToFile(const char* data, + size_t data_len) { return file_.AppendDataToFile(data, data_len); } -net::Error SaveFile::Rename(const FilePath& full_path) { +content::DownloadInterruptReason SaveFile::Rename(const FilePath& full_path) { return file_.Rename(full_path); } diff --git a/content/browser/download/save_file.h b/content/browser/download/save_file.h index cf0bf64..fdcc33f 100644 --- a/content/browser/download/save_file.h +++ b/content/browser/download/save_file.h @@ -24,9 +24,10 @@ class SaveFile { virtual ~SaveFile(); // BaseFile delegated functions. - net::Error Initialize(); - net::Error AppendDataToFile(const char* data, size_t data_len); - net::Error Rename(const FilePath& full_path); + content::DownloadInterruptReason Initialize(); + content::DownloadInterruptReason AppendDataToFile(const char* data, + size_t data_len); + content::DownloadInterruptReason Rename(const FilePath& full_path); void Detach(); void Cancel(); void Finish(); diff --git a/content/browser/download/save_file_manager.cc b/content/browser/download/save_file_manager.cc index d4fd8bf..52b24cb 100644 --- a/content/browser/download/save_file_manager.cc +++ b/content/browser/download/save_file_manager.cc @@ -242,7 +242,7 @@ void SaveFileManager::UpdateSaveProgress(int save_id, DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); SaveFile* save_file = LookupSaveFile(save_id); if (save_file) { - net::Error write_success = + content::DownloadInterruptReason reason = save_file->AppendDataToFile(data->data(), data_len); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, @@ -250,7 +250,7 @@ void SaveFileManager::UpdateSaveProgress(int save_id, this, save_file->save_id(), save_file->BytesSoFar(), - write_success == net::OK)); + reason == content::DOWNLOAD_INTERRUPT_REASON_NONE)); } } |