1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/download/base_file.h" 6 7#include "base/file_util.h" 8#include "base/format_macros.h" 9#include "base/logging.h" 10#include "base/stringprintf.h" 11#include "crypto/secure_hash.h" 12#include "net/base/file_stream.h" 13#include "net/base/net_errors.h" 14#include "chrome/browser/download/download_util.h" 15#include "content/browser/browser_thread.h" 16 17#if defined(OS_WIN) 18#include "chrome/common/win_safe_util.h" 19#elif defined(OS_MACOSX) 20#include "chrome/browser/cocoa/file_metadata.h" 21#endif 22 23BaseFile::BaseFile(const FilePath& full_path, 24 const GURL& source_url, 25 const GURL& referrer_url, 26 int64 received_bytes, 27 const linked_ptr<net::FileStream>& file_stream) 28 : full_path_(full_path), 29 source_url_(source_url), 30 referrer_url_(referrer_url), 31 file_stream_(file_stream), 32 bytes_so_far_(received_bytes), 33 power_save_blocker_(true), 34 calculate_hash_(false), 35 detached_(false) { 36 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 37 memset(sha256_hash_, 0, sizeof(sha256_hash_)); 38} 39 40BaseFile::~BaseFile() { 41 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 42 if (detached_) 43 Close(); 44 else 45 Cancel(); // Will delete the file. 46} 47 48bool BaseFile::Initialize(bool calculate_hash) { 49 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 50 DCHECK(!detached_); 51 52 calculate_hash_ = calculate_hash; 53 54 if (calculate_hash_) 55 secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256)); 56 57 if (!full_path_.empty() || 58 download_util::CreateTemporaryFileForDownload(&full_path_)) 59 return Open(); 60 return false; 61} 62 63bool BaseFile::AppendDataToFile(const char* data, size_t data_len) { 64 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 65 DCHECK(!detached_); 66 67 if (!file_stream_.get()) 68 return false; 69 70 // TODO(phajdan.jr): get rid of this check. 71 if (data_len == 0) 72 return true; 73 74 bytes_so_far_ += data_len; 75 76 // TODO(phajdan.jr): handle errors on file writes. http://crbug.com/58355 77 size_t written = file_stream_->Write(data, data_len, NULL); 78 if (written != data_len) 79 return false; 80 81 if (calculate_hash_) 82 secure_hash_->Update(data, data_len); 83 84 return true; 85} 86 87bool BaseFile::Rename(const FilePath& new_path) { 88 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 89 90 // Save the information whether the download is in progress because 91 // it will be overwritten by closing the file. 92 bool saved_in_progress = in_progress(); 93 94 // If the new path is same as the old one, there is no need to perform the 95 // following renaming logic. 96 if (new_path == full_path_) { 97 // Don't close the file if we're not done (finished or canceled). 98 if (!saved_in_progress) 99 Close(); 100 101 return true; 102 } 103 104 Close(); 105 106 file_util::CreateDirectory(new_path.DirName()); 107 108#if defined(OS_WIN) 109 // We cannot rename because rename will keep the same security descriptor 110 // on the destination file. We want to recreate the security descriptor 111 // with the security that makes sense in the new path. 112 if (!file_util::RenameFileAndResetSecurityDescriptor(full_path_, new_path)) 113 return false; 114#elif defined(OS_POSIX) 115 { 116 // Similarly, on Unix, we're moving a temp file created with permissions 117 // 600 to |new_path|. Here, we try to fix up the destination file with 118 // appropriate permissions. 119 struct stat st; 120 // First check the file existence and create an empty file if it doesn't 121 // exist. 122 if (!file_util::PathExists(new_path)) 123 file_util::WriteFile(new_path, "", 0); 124 bool stat_succeeded = (stat(new_path.value().c_str(), &st) == 0); 125 126 // TODO(estade): Move() falls back to copying and deleting when a simple 127 // rename fails. Copying sucks for large downloads. crbug.com/8737 128 if (!file_util::Move(full_path_, new_path)) 129 return false; 130 131 if (stat_succeeded) 132 chmod(new_path.value().c_str(), st.st_mode); 133 } 134#endif 135 136 full_path_ = new_path; 137 138 // We don't need to re-open the file if we're done (finished or canceled). 139 if (!saved_in_progress) 140 return true; 141 142 if (!Open()) 143 return false; 144 145 return true; 146} 147 148void BaseFile::Detach() { 149 detached_ = true; 150} 151 152void BaseFile::Cancel() { 153 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 154 DCHECK(!detached_); 155 156 Close(); 157 158 if (!full_path_.empty()) 159 file_util::Delete(full_path_, false); 160} 161 162void BaseFile::Finish() { 163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 164 165 if (calculate_hash_) 166 secure_hash_->Finish(sha256_hash_, kSha256HashLen); 167 168 Close(); 169} 170 171bool BaseFile::GetSha256Hash(std::string* hash) { 172 DCHECK(!detached_); 173 if (!calculate_hash_ || in_progress()) 174 return false; 175 hash->assign(reinterpret_cast<const char*>(sha256_hash_), 176 sizeof(sha256_hash_)); 177 return true; 178} 179 180void BaseFile::AnnotateWithSourceInformation() { 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 182 DCHECK(!detached_); 183 184#if defined(OS_WIN) 185 // Sets the Zone to tell Windows that this file comes from the internet. 186 // We ignore the return value because a failure is not fatal. 187 win_util::SetInternetZoneIdentifier(full_path_); 188#elif defined(OS_MACOSX) 189 file_metadata::AddQuarantineMetadataToFile(full_path_, source_url_, 190 referrer_url_); 191 file_metadata::AddOriginMetadataToFile(full_path_, source_url_, 192 referrer_url_); 193#endif 194} 195 196bool BaseFile::Open() { 197 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 198 DCHECK(!detached_); 199 DCHECK(!full_path_.empty()); 200 201 // Create a new file stream if it is not provided. 202 if (!file_stream_.get()) { 203 file_stream_.reset(new net::FileStream); 204 if (file_stream_->Open(full_path_, 205 base::PLATFORM_FILE_OPEN_ALWAYS | 206 base::PLATFORM_FILE_WRITE) != net::OK) { 207 file_stream_.reset(); 208 return false; 209 } 210 211 // We may be re-opening the file after rename. Always make sure we're 212 // writing at the end of the file. 213 if (file_stream_->Seek(net::FROM_END, 0) < 0) { 214 file_stream_.reset(); 215 return false; 216 } 217 } 218 219#if defined(OS_WIN) 220 AnnotateWithSourceInformation(); 221#endif 222 return true; 223} 224 225void BaseFile::Close() { 226 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 227 if (file_stream_.get()) { 228#if defined(OS_CHROMEOS) 229 // Currently we don't really care about the return value, since if it fails 230 // theres not much we can do. But we might in the future. 231 file_stream_->Flush(); 232#endif 233 file_stream_->Close(); 234 file_stream_.reset(); 235 } 236} 237 238std::string BaseFile::DebugString() const { 239 return base::StringPrintf("{ source_url_ = \"%s\"" 240 " full_path_ = \"%" PRFilePath "\"" 241 " bytes_so_far_ = %" PRId64 " detached_ = %c }", 242 source_url_.spec().c_str(), 243 full_path_.value().c_str(), 244 bytes_so_far_, 245 detached_ ? 'T' : 'F'); 246} 247