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