blob: f05eff732b5320d48e41b51fea50b32f4592a6a4 [file] [log] [blame]
// Copyright 2018 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/chrome_cleaner/os/file_path_sanitization.h"
#include <shlobj.h>
#include <utility>
#include "base/base_paths_win.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
namespace chrome_cleaner {
namespace sanitization_internal {
using chrome_cleaner::CsidlToPathServiceKey;
// This was added because some tests override the registry causing
// SHGetFolderPath (which was used to test if InitilizeDiskUtil() was called) to
// fail. Eventually it should be replaced by a class which properly handles the
// initialization checking.
//
// The default value of this flag should be false. Once
// InitializeFilePathSanitization()() is
// called, it will be set to true. DCHECK should be used to verify it is true
// whenever the code depends on OffsetCSIDLToPath() being registered with
// PathService().
bool initialize_file_path_sanitization_called = false;
// This data structure represents replacement rules, inorder, used by
// SanitizePath() and tests. It represents the relationship of PathService keys
// to labels. The PathService keys are linked to CSIDL values used in PUP
// footprints.
//
// An offset of PATH_CSIDL_START is added to the CSIDL values to map to a range
// which doesn't collide with other PathService providers.
const int PATH_CSIDL_START = 900;
const int PATH_CSIDL_END = PATH_CSIDL_START + 256;
const struct rewrite_rule_kvpair rewrite_rules[] = {
{CsidlToPathServiceKey(CSIDL_PROGRAM_FILES_COMMON),
L"CSIDL_PROGRAM_FILES_COMMON"},
// 32-bit 32-bit on 64-bit 64-bit on 64-bit
// CSIDL_PROGRAM_FILES 1 2 1
// CSIDL_PROGRAM_FILESX86 1* 2 2
// DIR_PROGRAM_FILES6432 1 1 1
// 1 - C:\Program Files 2 - C:\Program Files (x86)
// *CSIDL_PROGRAM_FILESX86 is not valid for Windows XP.
{CsidlToPathServiceKey(CSIDL_PROGRAM_FILES), L"CSIDL_PROGRAM_FILES"},
{CsidlToPathServiceKey(CSIDL_PROGRAM_FILESX86), L"CSIDL_PROGRAM_FILES"},
{base::DIR_PROGRAM_FILES6432, L"CSIDL_PROGRAM_FILES"},
// Child of CSIDL_COMMON_PROGRAMS.
{CsidlToPathServiceKey(CSIDL_COMMON_STARTUP), L"CSIDL_COMMON_STARTUP"},
// Child of CSIDL_COMMON_STARTMENU.
{CsidlToPathServiceKey(CSIDL_COMMON_PROGRAMS), L"CSIDL_COMMON_PROGRAMS"},
// Child of CSIDL_COMMON_APPDATA.
{CsidlToPathServiceKey(CSIDL_COMMON_STARTMENU), L"CSIDL_COMMON_STARTMENU"},
{CsidlToPathServiceKey(CSIDL_COMMON_APPDATA), L"CSIDL_COMMON_APPDATA"},
{CsidlToPathServiceKey(CSIDL_INTERNET_CACHE), L"CSIDL_INTERNET_CACHE"},
// Child of CSIDL_PROGRAMS.
{CsidlToPathServiceKey(CSIDL_STARTUP), L"CSIDL_STARTUP"},
// Child of CSIDL_STARTMENU.
{CsidlToPathServiceKey(CSIDL_PROGRAMS), L"CSIDL_PROGRAMS"},
// Child of CSIDL_LOCAL_APP_DATA
{base::DIR_TEMP, L"%TEMP%"},
// Child of CSIDL_APPDATA.
{CsidlToPathServiceKey(CSIDL_LOCAL_APPDATA), L"CSIDL_LOCAL_APPDATA"},
// Child of CSIDL_APPDATA.
{CsidlToPathServiceKey(CSIDL_STARTMENU), L"CSIDL_STARTMENU"},
{CsidlToPathServiceKey(CSIDL_APPDATA), L"CSIDL_APPDATA"},
// Child of CSIDL_WINDOWS.
{CsidlToPathServiceKey(CSIDL_SYSTEM), L"CSIDL_SYSTEM"},
{CsidlToPathServiceKey(CSIDL_SYSTEMX86), L"CSIDL_SYSTEM"},
{CsidlToPathServiceKey(CSIDL_WINDOWS), L"CSIDL_WINDOWS"},
// Child of CSIDL_PROFILE.
{CsidlToPathServiceKey(CSIDL_DESKTOP), L"CSIDL_DESKTOP"},
// Child of CSIDL_PROFILE.
{CsidlToPathServiceKey(CSIDL_COMMON_FAVORITES), L"CSIDL_COMMON_FAVORITES"},
{CsidlToPathServiceKey(CSIDL_PROFILE), L"CSIDL_PROFILE"},
{CsidlToPathServiceKey(CSIDL_COMMON_DESKTOPDIRECTORY),
L"CSIDL_COMMON_DESKTOPDIRECTORY"},
{CsidlToPathServiceKey(CSIDL_COMMON_DOCUMENTS), L"CSIDL_COMMON_DOCUMENTS"},
{0, nullptr} // This marks the end of the array.
};
} // namespace sanitization_internal
namespace {
// To make git cl lint happy.
const size_t kMaxPath = _MAX_PATH;
// Retrieves a special_path from PathService and returns true if path is a child
// path.
bool PathIsChildOfSpecialPath(int special_path_id,
const base::FilePath& normalized_path,
base::FilePath* special_path) {
if (!base::PathService::Get(special_path_id, special_path)) {
return false;
}
if (special_path_id < sanitization_internal::PATH_CSIDL_START ||
special_path_id >= sanitization_internal::PATH_CSIDL_END) {
base::FilePath lowercase_rule_path =
base::FilePath(base::ToLowerASCII(special_path->value()));
// If normalized_path doesn't exist, the conversion to long_path will
// fail and there may be short path components converted to lowercase in
// normalized_path. An example where this occurs is base::dir_temp.
if (lowercase_rule_path.IsParent(normalized_path)) {
*special_path = lowercase_rule_path;
return true;
} else {
*special_path = NormalizePath(*special_path);
}
}
return special_path->IsParent(normalized_path);
}
base::FilePath SanitizePathImpl(const base::FilePath& path) {
// This check makes sure InitializeFilePathSanitization()() has already been
// called.
base::FilePath rule_path;
DCHECK(sanitization_internal::initialize_file_path_sanitization_called)
<< "InitializeFilePathSanitization()() must be called before "
"SanitizePathImpl()";
base::FilePath normalized_path = NormalizePath(path);
if (normalized_path.empty())
return normalized_path;
for (const auto* rule = sanitization_internal::rewrite_rules;
rule->path != nullptr; ++rule) {
if (PathIsChildOfSpecialPath(rule->id, normalized_path, &rule_path)) {
base::FilePath resulting_path(rule->path);
if (rule_path.AppendRelativePath(normalized_path, &resulting_path)) {
return resulting_path;
} else {
NOTREACHED() << "AppendRelativePath() failed inside SanitizePathImpl()";
}
}
}
// Nothing to sanitize, return the original path.
return normalized_path;
}
// This function is used with PathService::RegisterProvider() to map CSIDL
// values offset by PATH_CSIDL_START to their paths using the caching provided
// by PathService without state problems during unit tests.
bool OffsetCSIDLToPath(int csidl_with_offset, base::FilePath* path) {
if (csidl_with_offset < sanitization_internal::PATH_CSIDL_START ||
csidl_with_offset >= sanitization_internal::PATH_CSIDL_END) {
return false;
}
wchar_t special_folder_path[kMaxPath];
HRESULT hr = ::SHGetFolderPath(
nullptr, csidl_with_offset - sanitization_internal::PATH_CSIDL_START,
nullptr, SHGFP_TYPE_CURRENT, special_folder_path);
if (hr == S_OK) {
*path = NormalizePath(base::FilePath(special_folder_path));
return true;
}
return false;
}
} // namespace
void InitializeFilePathSanitization() {
// Only do this once.
static bool init_once = []() -> bool {
// Setup PathService to use OffsetCSIDLToPath so that SanitizePath can
// benefit from the caching provided in PathService.
base::PathService::RegisterProvider(&OffsetCSIDLToPath,
sanitization_internal::PATH_CSIDL_START,
sanitization_internal::PATH_CSIDL_END);
// Cache Paths to prevent concurrent calls to SHGetFolderPath.
for (int path_id = sanitization_internal::PATH_CSIDL_START;
path_id < sanitization_internal::PATH_CSIDL_END; ++path_id) {
base::FilePath tempPath;
base::PathService::Get(path_id, &tempPath);
}
sanitization_internal::initialize_file_path_sanitization_called = true;
return true;
}();
ANALYZER_ALLOW_UNUSED(init_once);
}
std::vector<base::FilePath> GetRewrittenPaths() {
std::vector<base::FilePath> paths;
for (const auto* rule = sanitization_internal::rewrite_rules;
rule->path != nullptr; ++rule) {
base::FilePath rule_path;
if (base::PathService::Get(rule->id, &rule_path))
paths.push_back(NormalizePath(rule_path));
}
return paths;
}
std::map<int, base::string16> PathKeyToSanitizeString() {
std::map<int, base::string16> path_key_to_sanitize_string;
for (const auto* rule = sanitization_internal::rewrite_rules;
rule->path != nullptr; ++rule) {
path_key_to_sanitize_string.insert(std::make_pair(rule->id, rule->path));
}
return path_key_to_sanitize_string;
}
int CsidlToPathServiceKey(int CSIDL) {
return sanitization_internal::PATH_CSIDL_START + CSIDL;
}
base::FilePath NormalizePath(const base::FilePath& path) {
base::string16 long_path;
ConvertToLongPath(path.value(), &long_path);
return base::FilePath(base::ToLowerASCII(long_path));
}
void ConvertToLongPath(const base::string16& path, base::string16* long_path) {
DCHECK(long_path);
DWORD long_path_len = ::GetLongPathName(path.c_str(), nullptr, 0);
if (long_path_len > 0UL) {
long_path_len = ::GetLongPathName(
path.c_str(), base::WriteInto(long_path, long_path_len), long_path_len);
DCHECK_GT(long_path_len, 0UL);
} else {
*long_path = path;
}
}
base::string16 SanitizePath(const base::FilePath& path) {
return SanitizePathImpl(path).value();
}
base::string16 SanitizeCommandLine(const base::CommandLine& command_line) {
base::FilePath sanitized_program =
SanitizePathImpl(command_line.GetProgram());
base::CommandLine sanitized_command_line(sanitized_program);
for (const auto& s : command_line.GetSwitches()) {
sanitized_command_line.AppendSwitchNative(
s.first, SanitizePath(base::FilePath(s.second)));
}
for (const auto& arg : command_line.GetArgs()) {
sanitized_command_line.AppendArgNative(SanitizePath(base::FilePath(arg)));
}
return sanitized_command_line.GetCommandLineString();
}
base::FilePath ExpandSpecialFolderPath(int csidl,
const base::FilePath& input_path) {
// This check makes sure ExpandSpecialFolderPath() has already been called.
base::FilePath special_folder_path;
DCHECK(sanitization_internal::initialize_file_path_sanitization_called)
<< "InitializeFilePathSanitization()() must be called before "
"ExpandSpecialFolderPath()";
// No need to expand an absolute path, |csidl| is simply ignored in that case.
if (input_path.IsAbsolute())
return input_path;
if (base::PathService::Get(CsidlToPathServiceKey(csidl),
&special_folder_path)) {
return base::FilePath(special_folder_path).Append(input_path);
}
return base::FilePath();
}
} // namespace chrome_cleaner