blob: 668db878b2b9c05b033942220aa05c6b7d252eb9 [file] [log] [blame]
// Copyright 2007-2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
//
// The download manager uses the network request to download the remote file.
// When running as local system, the network request impersonates one of the
// logged on users. To save the file, the network request needs write access to
// a directory, both when running impersonated and not.
// The directory is obtained by calling SHGetFolderLocation with
// CSIDL_COMMON_APPDATA. In order to ensure the directory is accessible
// even in cases when impersonatation is used, BuildUniqueDownloadFilePath
// impersonates before calling SHGetFolderLocation.
//
// Once the download is complete, The download manager copies the file to either
// the machine secure location or the user secure location and then
// it validates the hash.
#include "omaha/worker/download_manager.h"
#include <vector>
#include <algorithm>
#include "omaha/common/debug.h"
#include "omaha/common/error.h"
#include "omaha/common/file.h"
#include "omaha/common/logging.h"
#include "omaha/common/path.h"
#include "omaha/common/scoped_impersonation.h"
#include "omaha/common/string.h"
#include "omaha/common/user_rights.h"
#include "omaha/common/utils.h"
#include "omaha/goopdate/config_manager.h"
#include "omaha/goopdate/const_goopdate.h"
#include "omaha/goopdate/goopdate_utils.h"
#include "omaha/goopdate/resource.h"
#include "omaha/net/bits_request.h"
#include "omaha/net/browser_request.h"
#include "omaha/net/http_client.h"
#include "omaha/net/network_request.h"
#include "omaha/net/net_utils.h"
#include "omaha/net/simple_request.h"
#include "omaha/worker/job.h"
#include "omaha/worker/worker_metrics.h"
namespace omaha {
namespace {
// Creates and initializes an instance of the NetworkRequest for the
// Downloadmanager to use. Defines a fallback chain: BITS, WinHttp, browser.
NetworkRequest* CreateNetworkRequest(bool is_logged_on) {
const NetworkConfig::Session& session(NetworkConfig::Instance().session());
NetworkRequest* network_request(new NetworkRequest(session));
// TODO(omaha): provide a mechanism for different timeout values in
// silent and interactive downloads.
// TODO(omaha): background downloads are not supported yet.
// BITS transfers files only when the job owner is logged on. If the
// process "Run As" another user, an empty BITS job gets created in suspended
// state but there is no way to manipulate the job, nor cancel it.
if (is_logged_on) {
BitsRequest* bits_request(new BitsRequest);
bits_request->set_minimum_retry_delay(60);
bits_request->set_no_progress_timeout(15);
network_request->AddHttpRequest(bits_request);
}
network_request->AddHttpRequest(new SimpleRequest);
network_request->AddHttpRequest(new BrowserRequest);
network_request->set_num_retries(1);
return network_request;
}
} // namespace
DownloadManager::DownloadManager(bool is_machine)
: job_(NULL),
is_machine_(is_machine),
impersonation_token_(NULL),
is_logged_on_(false) {
HRESULT hr = IsUserLoggedOn(&is_logged_on_);
// Assumes the caller is not logged on if the function failed.
ASSERT1(SUCCEEDED(hr) || !is_logged_on_);
http_client_.reset(CreateHttpClient());
ASSERT1(http_client_.get());
}
void DownloadManager::SetErrorInfo(HRESULT hr) {
ASSERT1(job_);
CString msg;
// TODO(omaha): job_->app_data().display_name() may not be correct
// for bundles.
if (!goopdate_utils::FormatMessageForNetworkError(
hr,
job_->app_data().display_name(),
&msg)) {
msg.FormatMessage(IDS_DOWNLOAD_ERROR, hr);
}
error_info_ = CompletionInfo(COMPLETION_ERROR, hr, msg);
}
HRESULT DownloadManager::DownloadFile(Job* job) {
ASSERT1(job);
ASSERT1(job_ == NULL);
ASSERT1(ConfigManager::Instance()->CanUseNetwork(is_machine_));
++metric_worker_download_total;
job_ = job;
HRESULT hr = DownloadCurrentJob();
if (SUCCEEDED(hr)) {
++metric_worker_download_succeeded;
}
job_ = NULL;
return hr;
}
HRESULT DownloadManager::DownloadCurrentJob() {
ASSERT1(job_);
HRESULT hr = BuildUniqueDownloadFilePath(&local_download_file_path_);
if (FAILED(hr)) {
CORE_LOG(LW,
(_T("[BuildUniqueDownloadFilePath failed][0x%08x]"), hr));
SetErrorInfo(hr);
return hr;
}
network_request_.reset(CreateNetworkRequest(is_logged_on_));
network_request_->set_low_priority(job_->is_background());
CString path = is_machine_ ?
ConfigManager::Instance()->GetMachineSecureDownloadStorageDir() :
ConfigManager::Instance()->GetUserDownloadStorageDir();
if (IsCached(path)) {
OPT_LOG(L1, (_T("[Using cached version of the download file %s]"),
local_download_file_path_));
return S_OK;
}
network_request_->set_callback(job_);
OPT_LOG(L1, (_T("[Starting file download from %s to %s]"),
job_->response_data().url(),
local_download_file_path_));
hr = network_request_->DownloadFile(job_->response_data().url(),
local_download_file_path_);
if (FAILED(hr)) {
goopdate_utils::AddNetworkRequestDataToEventLog(network_request_.get(), hr);
SetErrorInfo(hr);
}
VERIFY1(SUCCEEDED(network_request_->Close()));
if (FAILED(hr)) {
return hr;
}
hr = MoveFile();
if (FAILED(hr)) {
SetErrorInfo(hr);
return hr;
}
hr = ValidateDownloadedFile(local_download_file_path_);
if (FAILED(hr)) {
CString msg;
msg.FormatMessage(IDS_DOWNLOAD_HASH_MISMATCH, hr);
error_info_ = CompletionInfo(COMPLETION_ERROR, hr, msg);
LogValidationFailure();
return hr;
}
return S_OK;
}
void DownloadManager::LogValidationFailure() const {
const int kDownloadFileBytesToLog = 256;
bool exists = File::Exists(local_download_file_path_);
uint32 file_size(0);
std::vector<char> download_file_bytes(kDownloadFileBytesToLog + 1);
if (exists) {
if (FAILED(File::GetFileSizeUnopen(local_download_file_path_,
&file_size))) {
return;
}
File downloaded_file;
if (SUCCEEDED(downloaded_file.Open(local_download_file_path_,
false, false))) {
uint32 bytes_read = 0;
if (SUCCEEDED(downloaded_file.ReadFromStartOfFile(
kDownloadFileBytesToLog,
reinterpret_cast<unsigned char*>(&download_file_bytes.front()),
&bytes_read))) {
download_file_bytes.resize(bytes_read);
std::replace_if(download_file_bytes.begin(), download_file_bytes.end(),
std::not1(std::ptr_fun(isprint)), '.');
download_file_bytes.push_back('\0');
}
}
}
REPORT_LOG(L1, (_T("[DownloadValidationFail filename=%s exists=%d size=%d ")
_T("expected size=%d expected hash=%s filebytes=%hS]"),
local_download_file_path_,
exists,
file_size,
job_->response_data().size(),
job_->response_data().hash(),
&download_file_bytes.front()));
}
HRESULT DownloadManager::Cancel() {
return network_request_.get() ? network_request_->Cancel() : S_OK;
}
// The installer is initially downloaded to a temporary unique name.
// Once the download succeeds the file is copied to
// DownloadDir\<Guid>\<name> where
// DownloadDir = User or machine download dir returned by ConfigManager.
// guid = Guid used for temp name.
// name = Name specified in the update response.
// The reason to copy the file using a sub-directory structure is to account
// for the case where the same file is downloaded by multiple processes or
// threads.
HRESULT DownloadManager::BuildUniqueDownloadFilePath(CString* file) const {
ASSERT1(file);
// Impersonate the user if a valid impersonation token is presented.
// Continue unimpersonated if the impersonation fails. We do the
// impersonation here to get the correct download folder for
// impersonated clients. (For more information refer to the comment
// at the top of the file.)
scoped_impersonation impersonate_user(impersonation_token_);
if (impersonation_token_) {
DWORD result = impersonate_user.result();
ASSERT(result == ERROR_SUCCESS, (_T("impersonation failed %d"), result));
}
GUID guid(GUID_NULL);
HRESULT hr = ::CoCreateGuid(&guid);
if (FAILED(hr)) {
CORE_LOG(L3, (_T("[CoCreateGuid failed 0x%08x]"), hr));
return hr;
}
CString path(ConfigManager::Instance()->GetTempDownloadDir());
*file = ConcatenatePath(path, GuidToString(guid));
if (file->IsEmpty()) {
ASSERT1(false);
return GOOPDATEDOWNLOAD_E_UNIQUE_FILE_PATH_EMPTY;
}
return S_OK;
}
HRESULT DownloadManager::GetFileNameFromDownloadUrl(const CString& url,
CString* file_name) const {
CORE_LOG(L3, (_T("[DownloadManager::GetFileNameFromDownloadUrl]")));
ASSERT1(job_);
ASSERT1(http_client_.get());
ASSERT1(file_name);
CString url_path;
int port = 0;
CString extra_info;
HRESULT hr = http_client_->CrackUrl(url, 0, NULL, NULL, &port,
&url_path, &extra_info);
if (FAILED(hr)) {
OPT_LOG(LE, (_T("[CrackUrl failed 0x%08x]"), hr));
return GOOPDATEDOWNLOAD_E_CRACKURL_FAILED;
}
int start_file_name_idx = url_path.ReverseFind(_T('/'));
if (start_file_name_idx == -1) {
CORE_LOG(LW, (_T("[No filename found in download url.]")));
return GOOPDATEDOWNLOAD_E_INVALID_PATH;
}
ASSERT1(url_path.GetLength() >= start_file_name_idx - 1);
CString dst_file_name =
url_path.Right(url_path.GetLength() - start_file_name_idx - 1);
if (dst_file_name.IsEmpty()) {
OPT_LOG(LE, (_T("[Empty filename in download url]")));
return GOOPDATEDOWNLOAD_E_FILE_NAME_EMPTY;
}
ASSERT1(!dst_file_name.IsEmpty());
*file_name = dst_file_name;
return S_OK;
}
bool DownloadManager::IsCached(const CString& store) {
OPT_LOG(L3, (_T("[DownloadManager::IsCached]")));
ASSERT1(job_);
if (!job_->is_background()) {
return false;
}
CString dst_file_name;
HRESULT hr = GetFileNameFromDownloadUrl(job_->response_data().url(),
&dst_file_name);
if (FAILED(hr)) {
CORE_LOG(LE, (_T("[GetFileNameFromDownloadUrl failed][0x%08x]"), hr));
return false;
}
ASSERT1(!dst_file_name.IsEmpty());
std::vector<CString> files;
hr = FindFileRecursive(store, dst_file_name, &files);
if (FAILED(hr)) {
CORE_LOG(L3, (_T("[FindFileRecursive failed][0x%08x]"), hr));
return false;
}
for (size_t i = 0; i < files.size(); ++i) {
ASSERT1(File::Exists(files[i]));
if (SUCCEEDED(ValidateDownloadedFile(files[i]))) {
OPT_LOG(L2, (_T("[Found cached file %s.]"), files[i]));
local_download_file_path_ = files[i];
job_->set_download_file_name(local_download_file_path_);
return true;
} else {
OPT_LOG(L2, (_T("[Found cached file %s validation failed.]"), files[i]));
}
}
return false;
}
HRESULT DownloadManager::ValidateDownloadedFile(
const CString& file_name) const {
return goopdate_utils::ValidateDownloadedFile(file_name,
job_->response_data().hash(),
static_cast<uint32>(job_->response_data().size()));
}
HRESULT DownloadManager::BuildDestinationDirectory(CString* dest_path) const {
ASSERT1(dest_path);
dest_path->Empty();
const CString path = is_machine_ ?
ConfigManager::Instance()->GetMachineSecureDownloadStorageDir() :
ConfigManager::Instance()->GetUserDownloadStorageDir();
CORE_LOG(L3, (_T("[Download Storage Dir][%s]"), path));
if (!File::Exists(path)) {
return GOOPDATEDOWNLOAD_E_STORAGE_DIR_NOT_EXIST;
}
GUID guid(GUID_NULL);
HRESULT hr = ::CoCreateGuid(&guid);
if (FAILED(hr)) {
OPT_LOG(LW, (_T("[CoCreateGuid failed 0x%08x]"), hr));
return hr;
}
CString destination_path = ConcatenatePath(path, GuidToString(guid));
if (destination_path.IsEmpty()) {
ASSERT1(false);
return GOOPDATEDOWNLOAD_E_DEST_PATH_EMPTY;
}
hr = CreateDir(destination_path, NULL);
if (FAILED(hr)) {
// Since the directory creation failed, we will fall back to the destination
// directory returned by the ConfigManager.
OPT_LOG(LW, (_T("[CreateDir '%s' failed][0x%08x]"), destination_path, hr));
destination_path = path;
}
OPT_LOG(L1, (_T("[The destination directory is '%s']"), destination_path));
*dest_path = destination_path;
return S_OK;
}
HRESULT DownloadManager::MoveFile() {
++metric_worker_download_move_total;
CString dest_path;
HRESULT hr = BuildDestinationDirectory(&dest_path);
if (FAILED(hr)) {
OPT_LOG(LW, (_T("[Build destination directory failed][0x%08x]"), hr));
return hr;
}
CORE_LOG(L3, (_T("[Download Directory][%s]"), dest_path));
ASSERT1(!dest_path.IsEmpty());
ASSERT1(File::Exists(dest_path));
CString dst_file_name;
hr = GetFileNameFromDownloadUrl(job_->response_data().url(),
&dst_file_name);
if (FAILED(hr)) {
CORE_LOG(LW, (_T("[GetFileNameFromDownloadUrl failed][0x%08x]"), hr));
return hr;
}
if (dst_file_name.IsEmpty()) {
ASSERT1(false);
return GOOPDATEDOWNLOAD_E_DEST_FILENAME_EMPTY;
}
CORE_LOG(L3, (_T("[Destination filename][%s]"), dst_file_name));
CString dest_file_path = ConcatenatePath(dest_path, dst_file_name);
if (dest_file_path.IsEmpty()) {
ASSERT1(false);
return GOOPDATEDOWNLOAD_E_DEST_FILE_PATH_EMPTY;
}
OPT_LOG(L1, (_T("[Moving download file from %s to %s]"),
local_download_file_path_, dest_file_path));
// Uses ::CopyFile. ::CopyFile, done without impersonation, will reset the
// ownership of the destination file, and make sure that it inherits ACEs from
// the new parent directory.
hr = File::Copy(local_download_file_path_, dest_file_path, true);
if (FAILED(hr)) {
OPT_LOG(LE, (_T("[Could not copy '%s' to '%s'][0x%08x]"),
local_download_file_path_, dest_file_path, hr));
job_->set_extra_code1(hr);
return GOOPDATEDOWNLOAD_E_FAILED_MOVE;
}
VERIFY1(SUCCEEDED(File::Remove(local_download_file_path_)));
local_download_file_path_ = dest_file_path;
job_->set_download_file_name(dest_file_path);
++metric_worker_download_move_succeeded;
return S_OK;
}
} // namespace omaha