// 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<net::FileStream>& 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<const char*>(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');
}