1 // Copyright (c) 2012 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 "content/browser/download/base_file.h" 6 7 #include "base/bind.h" 8 #include "base/files/file.h" 9 #include "base/files/file_util.h" 10 #include "base/format_macros.h" 11 #include "base/logging.h" 12 #include "base/pickle.h" 13 #include "base/strings/stringprintf.h" 14 #include "base/threading/thread_restrictions.h" 15 #include "content/browser/download/download_interrupt_reasons_impl.h" 16 #include "content/browser/download/download_net_log_parameters.h" 17 #include "content/browser/download/download_stats.h" 18 #include "content/public/browser/browser_thread.h" 19 #include "content/public/browser/content_browser_client.h" 20 #include "crypto/secure_hash.h" 21 #include "net/base/net_errors.h" 22 23 namespace content { 24 25 // This will initialize the entire array to zero. 26 const unsigned char BaseFile::kEmptySha256Hash[] = { 0 }; 27 28 BaseFile::BaseFile(const base::FilePath& full_path, 29 const GURL& source_url, 30 const GURL& referrer_url, 31 int64 received_bytes, 32 bool calculate_hash, 33 const std::string& hash_state_bytes, 34 base::File file, 35 const net::BoundNetLog& bound_net_log) 36 : full_path_(full_path), 37 source_url_(source_url), 38 referrer_url_(referrer_url), 39 file_(file.Pass()), 40 bytes_so_far_(received_bytes), 41 start_tick_(base::TimeTicks::Now()), 42 calculate_hash_(calculate_hash), 43 detached_(false), 44 bound_net_log_(bound_net_log) { 45 memcpy(sha256_hash_, kEmptySha256Hash, crypto::kSHA256Length); 46 if (calculate_hash_) { 47 secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256)); 48 if ((bytes_so_far_ > 0) && // Not starting at the beginning. 49 (!IsEmptyHash(hash_state_bytes))) { 50 Pickle hash_state(hash_state_bytes.c_str(), hash_state_bytes.size()); 51 PickleIterator data_iterator(hash_state); 52 secure_hash_->Deserialize(&data_iterator); 53 } 54 } 55 } 56 57 BaseFile::~BaseFile() { 58 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 59 if (detached_) 60 Close(); 61 else 62 Cancel(); // Will delete the file. 63 } 64 65 DownloadInterruptReason BaseFile::Initialize( 66 const base::FilePath& default_directory) { 67 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 68 DCHECK(!detached_); 69 70 if (full_path_.empty()) { 71 base::FilePath initial_directory(default_directory); 72 base::FilePath temp_file; 73 if (initial_directory.empty()) { 74 initial_directory = 75 GetContentClient()->browser()->GetDefaultDownloadDirectory(); 76 } 77 // |initial_directory| can still be empty if ContentBrowserClient returned 78 // an empty path for the downloads directory. 79 if ((initial_directory.empty() || 80 !base::CreateTemporaryFileInDir(initial_directory, &temp_file)) && 81 !base::CreateTemporaryFile(&temp_file)) { 82 return LogInterruptReason("Unable to create", 0, 83 DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); 84 } 85 full_path_ = temp_file; 86 } 87 88 return Open(); 89 } 90 91 DownloadInterruptReason BaseFile::AppendDataToFile(const char* data, 92 size_t data_len) { 93 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 94 DCHECK(!detached_); 95 96 // NOTE(benwells): The above DCHECK won't be present in release builds, 97 // so we log any occurences to see how common this error is in the wild. 98 if (detached_) 99 RecordDownloadCount(APPEND_TO_DETACHED_FILE_COUNT); 100 101 if (!file_.IsValid()) 102 return LogInterruptReason("No file stream on append", 0, 103 DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); 104 105 // TODO(phajdan.jr): get rid of this check. 106 if (data_len == 0) 107 return DOWNLOAD_INTERRUPT_REASON_NONE; 108 109 // The Write call below is not guaranteed to write all the data. 110 size_t write_count = 0; 111 size_t len = data_len; 112 const char* current_data = data; 113 while (len > 0) { 114 write_count++; 115 int write_result = file_.WriteAtCurrentPos(current_data, len); 116 DCHECK_NE(0, write_result); 117 118 // Report errors on file writes. 119 if (write_result < 0) 120 return LogSystemError("Write", logging::GetLastSystemErrorCode()); 121 122 // Update status. 123 size_t write_size = static_cast<size_t>(write_result); 124 DCHECK_LE(write_size, len); 125 len -= write_size; 126 current_data += write_size; 127 bytes_so_far_ += write_size; 128 } 129 130 RecordDownloadWriteSize(data_len); 131 RecordDownloadWriteLoopCount(write_count); 132 133 if (calculate_hash_) 134 secure_hash_->Update(data, data_len); 135 136 return DOWNLOAD_INTERRUPT_REASON_NONE; 137 } 138 139 DownloadInterruptReason BaseFile::Rename(const base::FilePath& new_path) { 140 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 141 DownloadInterruptReason rename_result = DOWNLOAD_INTERRUPT_REASON_NONE; 142 143 // If the new path is same as the old one, there is no need to perform the 144 // following renaming logic. 145 if (new_path == full_path_) 146 return DOWNLOAD_INTERRUPT_REASON_NONE; 147 148 // Save the information whether the download is in progress because 149 // it will be overwritten by closing the file. 150 bool was_in_progress = in_progress(); 151 152 bound_net_log_.BeginEvent( 153 net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED, 154 base::Bind(&FileRenamedNetLogCallback, &full_path_, &new_path)); 155 Close(); 156 base::CreateDirectory(new_path.DirName()); 157 158 // A simple rename wouldn't work here since we want the file to have 159 // permissions / security descriptors that makes sense in the new directory. 160 rename_result = MoveFileAndAdjustPermissions(new_path); 161 162 if (rename_result == DOWNLOAD_INTERRUPT_REASON_NONE) 163 full_path_ = new_path; 164 165 // Re-open the file if we were still using it regardless of the interrupt 166 // reason. 167 DownloadInterruptReason open_result = DOWNLOAD_INTERRUPT_REASON_NONE; 168 if (was_in_progress) 169 open_result = Open(); 170 171 bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED); 172 return rename_result == DOWNLOAD_INTERRUPT_REASON_NONE ? open_result 173 : rename_result; 174 } 175 176 void BaseFile::Detach() { 177 detached_ = true; 178 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED); 179 } 180 181 void BaseFile::Cancel() { 182 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 183 DCHECK(!detached_); 184 185 bound_net_log_.AddEvent(net::NetLog::TYPE_CANCELLED); 186 187 Close(); 188 189 if (!full_path_.empty()) { 190 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DELETED); 191 base::DeleteFile(full_path_, false); 192 } 193 194 Detach(); 195 } 196 197 void BaseFile::Finish() { 198 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 199 200 if (calculate_hash_) 201 secure_hash_->Finish(sha256_hash_, crypto::kSHA256Length); 202 203 Close(); 204 } 205 206 void BaseFile::SetClientGuid(const std::string& guid) { 207 client_guid_ = guid; 208 } 209 210 // OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations. 211 #if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX) 212 DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() { 213 return DOWNLOAD_INTERRUPT_REASON_NONE; 214 } 215 #endif 216 217 bool BaseFile::GetHash(std::string* hash) { 218 DCHECK(!detached_); 219 hash->assign(reinterpret_cast<const char*>(sha256_hash_), 220 sizeof(sha256_hash_)); 221 return (calculate_hash_ && !in_progress()); 222 } 223 224 std::string BaseFile::GetHashState() { 225 if (!calculate_hash_) 226 return std::string(); 227 228 Pickle hash_state; 229 if (!secure_hash_->Serialize(&hash_state)) 230 return std::string(); 231 232 return std::string(reinterpret_cast<const char*>(hash_state.data()), 233 hash_state.size()); 234 } 235 236 // static 237 bool BaseFile::IsEmptyHash(const std::string& hash) { 238 return (hash.size() == crypto::kSHA256Length && 239 0 == memcmp(hash.data(), kEmptySha256Hash, crypto::kSHA256Length)); 240 } 241 242 std::string BaseFile::DebugString() const { 243 return base::StringPrintf("{ source_url_ = \"%s\"" 244 " full_path_ = \"%" PRFilePath "\"" 245 " bytes_so_far_ = %" PRId64 246 " detached_ = %c }", 247 source_url_.spec().c_str(), 248 full_path_.value().c_str(), 249 bytes_so_far_, 250 detached_ ? 'T' : 'F'); 251 } 252 253 DownloadInterruptReason BaseFile::Open() { 254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 255 DCHECK(!detached_); 256 DCHECK(!full_path_.empty()); 257 258 bound_net_log_.BeginEvent( 259 net::NetLog::TYPE_DOWNLOAD_FILE_OPENED, 260 base::Bind(&FileOpenedNetLogCallback, &full_path_, bytes_so_far_)); 261 262 // Create a new file if it is not provided. 263 if (!file_.IsValid()) { 264 file_.Initialize( 265 full_path_, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE); 266 if (!file_.IsValid()) { 267 return LogNetError("Open", 268 net::FileErrorToNetError(file_.error_details())); 269 } 270 } 271 272 // We may be re-opening the file after rename. Always make sure we're 273 // writing at the end of the file. 274 int64 file_size = file_.Seek(base::File::FROM_END, 0); 275 if (file_size < 0) { 276 logging::SystemErrorCode error = logging::GetLastSystemErrorCode(); 277 ClearFile(); 278 return LogSystemError("Seek", error); 279 } else if (file_size > bytes_so_far_) { 280 // The file is larger than we expected. 281 // This is OK, as long as we don't use the extra. 282 // Truncate the file. 283 if (!file_.SetLength(bytes_so_far_) || 284 file_.Seek(base::File::FROM_BEGIN, bytes_so_far_) != bytes_so_far_) { 285 logging::SystemErrorCode error = logging::GetLastSystemErrorCode(); 286 ClearFile(); 287 return LogSystemError("Truncate", error); 288 } 289 } else if (file_size < bytes_so_far_) { 290 // The file is shorter than we expected. Our hashes won't be valid. 291 ClearFile(); 292 return LogInterruptReason("Unable to seek to last written point", 0, 293 DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT); 294 } 295 296 return DOWNLOAD_INTERRUPT_REASON_NONE; 297 } 298 299 void BaseFile::Close() { 300 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 301 302 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_CLOSED); 303 304 if (file_.IsValid()) { 305 // Currently we don't really care about the return value, since if it fails 306 // theres not much we can do. But we might in the future. 307 file_.Flush(); 308 ClearFile(); 309 } 310 } 311 312 void BaseFile::ClearFile() { 313 // This should only be called when we have a stream. 314 DCHECK(file_.IsValid()); 315 file_.Close(); 316 bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED); 317 } 318 319 DownloadInterruptReason BaseFile::LogNetError( 320 const char* operation, 321 net::Error error) { 322 bound_net_log_.AddEvent( 323 net::NetLog::TYPE_DOWNLOAD_FILE_ERROR, 324 base::Bind(&FileErrorNetLogCallback, operation, error)); 325 return ConvertNetErrorToInterruptReason(error, DOWNLOAD_INTERRUPT_FROM_DISK); 326 } 327 328 DownloadInterruptReason BaseFile::LogSystemError( 329 const char* operation, 330 logging::SystemErrorCode os_error) { 331 // There's no direct conversion from a system error to an interrupt reason. 332 base::File::Error file_error = base::File::OSErrorToFileError(os_error); 333 return LogInterruptReason( 334 operation, os_error, 335 ConvertFileErrorToInterruptReason(file_error)); 336 } 337 338 DownloadInterruptReason BaseFile::LogInterruptReason( 339 const char* operation, 340 int os_error, 341 DownloadInterruptReason reason) { 342 bound_net_log_.AddEvent( 343 net::NetLog::TYPE_DOWNLOAD_FILE_ERROR, 344 base::Bind(&FileInterruptedNetLogCallback, operation, os_error, reason)); 345 return reason; 346 } 347 348 } // namespace content 349