Chromium Code Reviews| Index: chrome/browser/metrics/antivirus_metrics_provider_win.cc |
| diff --git a/chrome/browser/metrics/antivirus_metrics_provider_win.cc b/chrome/browser/metrics/antivirus_metrics_provider_win.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..6ef07610bfe463eaaf26b547c6ded014da6f99f2 |
| --- /dev/null |
| +++ b/chrome/browser/metrics/antivirus_metrics_provider_win.cc |
| @@ -0,0 +1,251 @@ |
| +// Copyright 2016 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/metrics/antivirus_metrics_provider_win.h" |
| + |
| +#include <iwscapi.h> |
| +#include <stddef.h> |
| +#include <windows.h> |
| +#include <wscapi.h> |
| + |
| +#include <string> |
| + |
| +#include "base/bind.h" |
| +#include "base/callback.h" |
| +#include "base/feature_list.h" |
| +#include "base/file_version_info_win.h" |
| +#include "base/files/file_path.h" |
| +#include "base/files/file_util.h" |
| +#include "base/metrics/field_trial.h" |
| +#include "base/metrics/histogram.h" |
| +#include "base/path_service.h" |
| +#include "base/strings/string_util.h" |
| +#include "base/strings/sys_string_conversions.h" |
| +#include "base/task_runner_util.h" |
| +#include "base/threading/thread_restrictions.h" |
| +#include "base/version.h" |
| +#include "base/win/scoped_bstr.h" |
| +#include "base/win/scoped_com_initializer.h" |
| +#include "base/win/scoped_comptr.h" |
| +#include "base/win/windows_version.h" |
| +#include "chrome/common/channel_info.h" |
| +#include "components/metrics/proto/system_profile.pb.h" |
| +#include "components/variations/metrics_util.h" |
| +#include "components/version_info/version_info.h" |
| + |
| +namespace { |
| + |
| +bool ShouldReportFullNames() { |
| + // The expectation is that this will be disabled for the majority of users, |
| + // but this allows a small group to be enabled on other channels if there are |
| + // a large percentage of hashes collected on these channels that are not |
| + // resolved to names previously collected on Canary channel. |
| + bool enabled = |
| + base::FeatureList::IsEnabled(AntiVirusMetricsProvider::kFeature); |
| + |
| + if (chrome::GetChannel() == version_info::Channel::CANARY) |
| + return true; |
| + |
| + return enabled; |
| +} |
| + |
| +// Helper function for expanding all environment variables in |path|. |
| +std::wstring ExpandEnvironmentVariables(const std::wstring& path) { |
| + static const DWORD kMaxBuffer = 32 * 1024; // Max according to MSDN. |
| + std::wstring path_expanded; |
| + DWORD path_len = MAX_PATH; |
| + do { |
| + DWORD result = ExpandEnvironmentStrings( |
| + path.c_str(), base::WriteInto(&path_expanded, path_len), path_len); |
| + if (!result) { |
| + // Failed to expand variables. Return the original string. |
| + DPLOG(ERROR) << path; |
| + break; |
| + } |
| + if (result <= path_len) |
| + return path_expanded.substr(0, result - 1); |
| + path_len = result; |
| + } while (path_len < kMaxBuffer); |
| + |
| + return path; |
| +} |
| + |
| +// Helper function to take a |path| to a file, that might contain environment |
| +// strings, and read the file version information in |product_version|. Returns |
| +// true if it was possible to extract the file information correctly. |
| +bool GetProductVersion(std::wstring* path, std::string* product_version) { |
| + base::FilePath full_path(ExpandEnvironmentVariables(*path)); |
| + |
| +#if !defined(_WIN64) |
| + if (!base::PathExists(full_path)) { |
| + // On 32-bit builds, path might contain C:\Program Files (x86) instead of |
| + // C:\Program Files. |
| + base::ReplaceFirstSubstringAfterOffset(path, 0, L"%ProgramFiles%", |
| + L"%ProgramW6432%"); |
| + full_path = base::FilePath(ExpandEnvironmentVariables(*path)); |
| + } |
| +#endif // !defined(_WIN64) |
| + std::unique_ptr<FileVersionInfo> version_info( |
| + FileVersionInfo::CreateFileVersionInfo(full_path)); |
| + |
| + // It is not an error if the product version cannot be read, so continue in |
| + // this case. |
| + if (version_info.get()) { |
| + FileVersionInfoWin* version_info_win = |
| + static_cast<FileVersionInfoWin*>(version_info.get()); |
| + *product_version = |
| + std::move(base::SysWideToUTF8(version_info_win->product_version())); |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +} // namespace |
| + |
| +AntiVirusMetricsProvider::AntiVirusMetricsProvider( |
| + scoped_refptr<base::SequencedTaskRunner> file_thread) |
| + : file_thread_(file_thread), weak_ptr_factory_(this) {} |
| + |
| +AntiVirusMetricsProvider::~AntiVirusMetricsProvider() {} |
| + |
| +void AntiVirusMetricsProvider::ProvideSystemProfileMetrics( |
| + metrics::SystemProfileProto* system_profile_proto) { |
| + for (const auto& av_product : av_products_) { |
| + auto product = system_profile_proto->add_antivirus_product(); |
| + *product = std::move(av_product); |
|
Alexei Svitkine (slow)
2016/06/03 15:48:33
This doesn't look right to me. This function will
Will Harris
2016/06/03 20:51:30
so it turns out because I am using a const iterato
|
| + } |
| +} |
| + |
| +void AntiVirusMetricsProvider::GetAntiVirusMetrics( |
| + const base::Closure& done_callback) { |
| + base::PostTaskAndReplyWithResult( |
| + file_thread_.get(), FROM_HERE, |
| + base::Bind(&AntiVirusMetricsProvider::GetAntiVirusProductsOnFileThread), |
| + base::Bind(&AntiVirusMetricsProvider::GotAntiVirusProducts, |
| + weak_ptr_factory_.GetWeakPtr(), done_callback)); |
| +} |
| + |
| +// static |
| +AntiVirusMetricsProvider::AvProductList |
| +AntiVirusMetricsProvider::GetAntiVirusProductsOnFileThread() { |
| + AvProductList av_products; |
| + |
| + ResultCode result = FillAntiVirusProducts(&av_products); |
| + |
| + UMA_HISTOGRAM_ENUMERATION("AntiVirusMetricsProvider.Result", |
|
Alexei Svitkine (slow)
2016/06/03 15:48:33
Nit: Prefix the name with "UMA."
Will Harris
2016/06/03 20:51:30
Done.
|
| + result, |
| + RESULT_COUNT); |
| + |
| + return av_products; |
| +} |
| + |
| +void AntiVirusMetricsProvider::GotAntiVirusProducts( |
| + const base::Closure& done_callback, |
| + const AvProductList& av_products) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + av_products_ = av_products; |
| + done_callback.Run(); |
| +} |
| + |
| +// static |
| +AntiVirusMetricsProvider::ResultCode |
| +AntiVirusMetricsProvider::FillAntiVirusProducts(AvProductList* products) { |
| + AvProductList result_list; |
| + base::ThreadRestrictions::AssertIOAllowed(); |
| + base::win::ScopedCOMInitializer com_initializer; |
| + |
| + if (!com_initializer.succeeded()) |
| + return RESULT_FAILED_TO_INITIALIZE_COM; |
| + |
| + base::win::ScopedComPtr<IWSCProductList> product_list; |
| + HRESULT result = |
| + CoCreateInstance(__uuidof(WSCProductList), NULL, CLSCTX_INPROC_SERVER, |
| + __uuidof(IWSCProductList), product_list.ReceiveVoid()); |
| + if (FAILED(result)) |
| + return RESULT_FAILED_TO_CREATE_INSTANCE; |
| + |
| + result = product_list->Initialize(WSC_SECURITY_PROVIDER_ANTIVIRUS); |
| + if (FAILED(result)) |
| + return RESULT_FAILED_TO_INITIALIZE_PRODUCT_LIST; |
| + |
| + LONG product_count; |
| + result = product_list->get_Count(&product_count); |
| + if (FAILED(result)) |
| + return RESULT_FAILED_TO_GET_PRODUCT_COUNT; |
| + |
| + for (LONG i = 0; i < product_count; i++) { |
| + IWscProduct* product = nullptr; |
| + result = product_list->get_Item(i, &product); |
| + if (FAILED(result)) |
| + return RESULT_FAILED_TO_GET_ITEM; |
| + |
| + static_assert(metrics::SystemProfileProto::AntiVirusState:: |
| + SystemProfileProto_AntiVirusState_STATE_ON == |
| + static_cast<metrics::SystemProfileProto::AntiVirusState>( |
| + WSC_SECURITY_PRODUCT_STATE_ON), |
| + "proto and API values must be the same"); |
| + static_assert(metrics::SystemProfileProto::AntiVirusState:: |
| + SystemProfileProto_AntiVirusState_STATE_OFF == |
| + static_cast<metrics::SystemProfileProto::AntiVirusState>( |
| + WSC_SECURITY_PRODUCT_STATE_OFF), |
| + "proto and API values must be the same"); |
| + static_assert(metrics::SystemProfileProto::AntiVirusState:: |
| + SystemProfileProto_AntiVirusState_STATE_SNOOZED == |
| + static_cast<metrics::SystemProfileProto::AntiVirusState>( |
| + WSC_SECURITY_PRODUCT_STATE_SNOOZED), |
| + "proto and API values must be the same"); |
| + static_assert(metrics::SystemProfileProto::AntiVirusState:: |
| + SystemProfileProto_AntiVirusState_STATE_EXPIRED == |
| + static_cast<metrics::SystemProfileProto::AntiVirusState>( |
| + WSC_SECURITY_PRODUCT_STATE_EXPIRED), |
| + "proto and API values must be the same"); |
| + |
| + AvProduct av_product; |
| + WSC_SECURITY_PRODUCT_STATE product_state; |
| + result = product->get_ProductState(&product_state); |
| + if (FAILED(result)) |
| + return RESULT_FAILED_TO_GET_PRODUCT_STATE; |
| + |
| + if (!metrics::SystemProfileProto_AntiVirusState_IsValid(product_state)) |
| + return RESULT_PRODUCT_STATE_INVALID; |
| + |
| + av_product.set_product_state( |
| + static_cast<metrics::SystemProfileProto::AntiVirusState>( |
| + product_state)); |
| + |
| + base::win::ScopedBstr product_name; |
| + result = product->get_ProductName(product_name.Receive()); |
| + if (FAILED(result)) |
| + return RESULT_FAILED_TO_GET_PRODUCT_NAME; |
| + std::string name = |
| + base::SysWideToUTF8(std::wstring(product_name, product_name.Length())); |
| + product_name.Release(); |
| + if (ShouldReportFullNames()) |
| + av_product.set_product_name(name); |
| + av_product.set_product_name_hash(metrics::HashName(name)); |
| + |
| + base::win::ScopedBstr remediation_path; |
| + result = product->get_RemediationPath(remediation_path.Receive()); |
| + if (FAILED(result)) |
| + return RESULT_FAILED_TO_GET_REMEDIATION_PATH; |
| + std::wstring path_str(remediation_path, remediation_path.Length()); |
| + remediation_path.Release(); |
| + |
| + std::string product_version; |
| + // Not a failure if the product version cannot be read from the file on |
| + // disk. |
| + if (GetProductVersion(&path_str, &product_version)) { |
| + if (ShouldReportFullNames()) |
| + av_product.set_product_version(product_version); |
| + av_product.set_product_version_hash(metrics::HashName(product_version)); |
| + } |
| + |
| + result_list.push_back(av_product); |
| + } |
| + |
| + *products = std::move(result_list); |
| + |
| + return RESULT_SUCCESS; |
| +} |