blob: 3268861f749315b1d0ca4bf0e882dab8ca2a2816 [file] [log] [blame]
// Copyright 2019 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/download/mixed_content_download_blocking.h"
#include <string>
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/optional.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/common/chrome_features.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/origin_util.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
// Map the string file extension to the corresponding histogram enum.
InsecureDownloadExtensions GetExtensionEnumFromString(
const std::string& extension) {
if (extension.empty())
return InsecureDownloadExtensions::kNone;
auto lower_extension = base::ToLowerASCII(extension);
for (auto candidate : kExtensionsToEnum) {
if (candidate.extension == lower_extension)
return candidate.value;
}
return InsecureDownloadExtensions::kUnknown;
}
// Get the appropriate histogram metric name for the initiator/download security
// state combo.
std::string GetDownloadBlockingExtensionMetricName(
base::Optional<url::Origin> initiator,
bool dl_secure) {
if (!initiator.has_value()) {
if (dl_secure)
return kInsecureDownloadHistogramInitiatorUnknownTargetSecure;
return kInsecureDownloadHistogramInitiatorUnknownTargetInsecure;
}
if (initiator->GetURL().SchemeIsCryptographic()) {
if (dl_secure)
return kInsecureDownloadHistogramInitiatorSecureTargetSecure;
return kInsecureDownloadHistogramInitiatorSecureTargetInsecure;
}
if (dl_secure)
return kInsecureDownloadHistogramInitiatorInsecureTargetSecure;
return kInsecureDownloadHistogramInitiatorInsecureTargetInsecure;
}
// Get appropriate enum value for the initiator/download security state combo
// for histogram reporting.
InsecureDownloadSecurityStatus GetDownloadBlockingEnum(
base::Optional<url::Origin> initiator,
bool dl_secure) {
if (!initiator.has_value()) {
if (dl_secure)
return InsecureDownloadSecurityStatus::kInitiatorUnknownFileSecure;
return InsecureDownloadSecurityStatus::kInitiatorUnknownFileInsecure;
}
if (initiator->GetURL().SchemeIsCryptographic()) {
if (dl_secure)
return InsecureDownloadSecurityStatus::kInitiatorSecureFileSecure;
return InsecureDownloadSecurityStatus::kInitiatorSecureFileInsecure;
}
if (dl_secure)
return InsecureDownloadSecurityStatus::kInitiatorInsecureFileSecure;
return InsecureDownloadSecurityStatus::kInitiatorInsecureFileInsecure;
}
} // namespace
bool ShouldBlockFileAsMixedContent(const base::FilePath& path,
const download::DownloadItem& item) {
// Extensions must be in lower case! Extensions are compared against save path
// determined by Chrome prior to the user seeing a file picker.
const std::vector<std::string> kDefaultUnsafeExtensions = {
"exe", "scr", "msi", "vb", "dmg", "pkg", "crx",
"gz", "zip", "bz2", "rar", "7z", "tar",
};
// Evaluate download security
const GURL& dl_url = item.GetURL();
bool is_download_secure = content::IsOriginSecure(dl_url) ||
dl_url.SchemeIsBlob() || dl_url.SchemeIsFile();
bool is_redirect_chain_secure = true;
for (const auto& url : item.GetUrlChain()) {
if (!content::IsOriginSecure(url)) {
is_redirect_chain_secure = false;
break;
}
}
is_download_secure = is_download_secure && is_redirect_chain_secure;
// Check field trials for override of the unsafe extensions.
std::string field_trial_arg = base::GetFieldTrialParamValueByFeature(
features::kTreatUnsafeDownloadsAsActive,
features::kTreatUnsafeDownloadsAsActiveParamName);
std::vector<base::StringPiece> unsafe_extensions = base::SplitStringPiece(
field_trial_arg, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (unsafe_extensions.empty()) {
unsafe_extensions.insert(unsafe_extensions.end(),
kDefaultUnsafeExtensions.begin(),
kDefaultUnsafeExtensions.end());
}
auto initiator = item.GetRequestInitiator();
// Then see if that extension is blocked
bool found_blocked_extension = false;
#if defined(OS_WIN)
std::string extension(base::WideToUTF8(path.FinalExtension()));
#else
std::string extension(path.FinalExtension());
#endif
if (!extension.empty()) {
extension = extension.substr(1); // Omit leading dot.
for (const auto& unsafe_extension : unsafe_extensions) {
if (base::LowerCaseEqualsASCII(extension, unsafe_extension)) {
found_blocked_extension = true;
break;
}
}
}
base::UmaHistogramEnumeration(
GetDownloadBlockingExtensionMetricName(initiator, is_download_secure),
GetExtensionEnumFromString(extension));
base::UmaHistogramEnumeration(
kInsecureDownloadHistogramName,
GetDownloadBlockingEnum(initiator, is_download_secure));
if (!(initiator.has_value() && initiator->GetURL().SchemeIsCryptographic() &&
!is_download_secure && found_blocked_extension &&
base::FeatureList::IsEnabled(
features::kTreatUnsafeDownloadsAsActive))) {
return false;
}
content::WebContents* web_contents =
content::DownloadItemUtils::GetWebContents(&item);
if (web_contents) {
web_contents->GetMainFrame()->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
base::StringPrintf(
"Mixed Content: The site at '%s' was loaded over a secure "
"connection, but the file at '%s' was %s an insecure "
"connection. This file should be served over HTTPS.",
initiator->GetURL().spec().c_str(), item.GetURL().spec().c_str(),
(is_redirect_chain_secure ? "loaded over" : "redirected through")));
}
return true;
}