// Copyright (c) 2011 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 "chrome/browser/download/base_file.h" #include "base/file_util.h" #include "base/format_macros.h" #include "base/logging.h" #include "base/stringprintf.h" #include "crypto/secure_hash.h" #include "net/base/file_stream.h" #include "net/base/net_errors.h" #include "chrome/browser/download/download_util.h" #include "content/browser/browser_thread.h" #if defined(OS_WIN) #include "chrome/common/win_safe_util.h" #elif defined(OS_MACOSX) #include "chrome/browser/cocoa/file_metadata.h" #endif BaseFile::BaseFile(const FilePath& full_path, const GURL& source_url, const GURL& referrer_url, int64 received_bytes, const linked_ptr& file_stream) : full_path_(full_path), source_url_(source_url), referrer_url_(referrer_url), file_stream_(file_stream), bytes_so_far_(received_bytes), power_save_blocker_(true), calculate_hash_(false), detached_(false) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); memset(sha256_hash_, 0, sizeof(sha256_hash_)); } BaseFile::~BaseFile() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); if (detached_) Close(); else Cancel(); // Will delete the file. } bool BaseFile::Initialize(bool calculate_hash) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(!detached_); calculate_hash_ = calculate_hash; if (calculate_hash_) secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256)); if (!full_path_.empty() || download_util::CreateTemporaryFileForDownload(&full_path_)) return Open(); return false; } bool BaseFile::AppendDataToFile(const char* data, size_t data_len) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(!detached_); if (!file_stream_.get()) return false; // TODO(phajdan.jr): get rid of this check. if (data_len == 0) return true; bytes_so_far_ += data_len; // TODO(phajdan.jr): handle errors on file writes. http://crbug.com/58355 size_t written = file_stream_->Write(data, data_len, NULL); if (written != data_len) return false; if (calculate_hash_) secure_hash_->Update(data, data_len); return true; } bool BaseFile::Rename(const FilePath& new_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); // Save the information whether the download is in progress because // it will be overwritten by closing the file. bool saved_in_progress = in_progress(); // 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 true; } 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. if (!file_util::RenameFileAndResetSecurityDescriptor(full_path_, new_path)) return false; #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)) file_util::WriteFile(new_path, "", 0); bool stat_succeeded = (stat(new_path.value().c_str(), &st) == 0); // 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 false; if (stat_succeeded) chmod(new_path.value().c_str(), st.st_mode); } #endif full_path_ = new_path; // We don't need to re-open the file if we're done (finished or canceled). if (!saved_in_progress) return true; if (!Open()) return false; return true; } void BaseFile::Detach() { detached_ = true; } void BaseFile::Cancel() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(!detached_); Close(); if (!full_path_.empty()) file_util::Delete(full_path_, false); } void BaseFile::Finish() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); if (calculate_hash_) secure_hash_->Finish(sha256_hash_, kSha256HashLen); Close(); } bool BaseFile::GetSha256Hash(std::string* hash) { DCHECK(!detached_); if (!calculate_hash_ || in_progress()) return false; hash->assign(reinterpret_cast(sha256_hash_), sizeof(sha256_hash_)); return true; } 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_); #elif defined(OS_MACOSX) file_metadata::AddQuarantineMetadataToFile(full_path_, source_url_, referrer_url_); file_metadata::AddOriginMetadataToFile(full_path_, source_url_, referrer_url_); #endif } bool BaseFile::Open() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(!detached_); DCHECK(!full_path_.empty()); // Create a new file stream if it is not provided. if (!file_stream_.get()) { file_stream_.reset(new net::FileStream); if (file_stream_->Open(full_path_, base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_WRITE) != net::OK) { file_stream_.reset(); return false; } // We may be re-opening the file after rename. Always make sure we're // writing at the end of the file. if (file_stream_->Seek(net::FROM_END, 0) < 0) { file_stream_.reset(); return false; } } #if defined(OS_WIN) AnnotateWithSourceInformation(); #endif return true; } void BaseFile::Close() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); if (file_stream_.get()) { #if defined(OS_CHROMEOS) // Currently we don't really care about the return value, since if it fails // theres not much we can do. But we might in the future. file_stream_->Flush(); #endif file_stream_->Close(); file_stream_.reset(); } } 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'); }