// Copyright 2020 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 "weblayer/browser/profile_disk_operations.h" #include "base/files/file_enumerator.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "build/build_config.h" #include "components/base32/base32.h" #include "weblayer/common/weblayer_paths.h" namespace weblayer { // Variables named |name| is a string passed in from the embedder to identify a // profile. It can only contain alphanumeric and underscore. // // Variables named |dir_name| generally refers to the directory name of the // profile. It may be the |name| exactly, or it may be ., if a // profile is created with a name matching a profile marked for deletion. // |dir_name| is an implementation detail of this file and should not be exposed // as a concept out of this file. namespace { // Cannot be part of a valid name. This prevents the client ever specifying a // name that collides a different one with a suffix. constexpr char kSuffixDelimiter = '.'; bool IsValidProfileNameChar(char c) { return base::IsAsciiDigit(c) || base::IsAsciiAlpha(c) || c == '_'; } // Return the data path directory to profiles. base::FilePath GetProfileRootDataDir() { base::FilePath path; CHECK(base::PathService::Get(DIR_USER_DATA, &path)); return path.AppendASCII("profiles"); } base::FilePath GetProfileMarkerRootDataDir() { base::FilePath path; CHECK(base::PathService::Get(DIR_USER_DATA, &path)); path = path.AppendASCII("profiles_to_delete"); base::CreateDirectory(path); return path; } base::FilePath GetDataPathFromDirName(const std::string& dir_name) { return GetProfileRootDataDir().AppendASCII(dir_name.c_str()); } #if BUILDFLAG(IS_POSIX) base::FilePath GetCachePathFromDirName(const std::string& dir_name) { base::FilePath cache_path; CHECK(base::PathService::Get(base::DIR_CACHE, &cache_path)); cache_path = cache_path.AppendASCII("profiles").AppendASCII(dir_name.c_str()); return cache_path; } #endif // BUILDFLAG(IS_POSIX) } // namespace ProfileInfo::ProfileInfo(bool is_incognito, const std::string& name, const base::FilePath& data_path, const base::FilePath& cache_path) : is_incognito(is_incognito), name(name), data_path(data_path), cache_path(cache_path) {} ProfileInfo::ProfileInfo() = default; ProfileInfo::ProfileInfo(const ProfileInfo&) = default; ProfileInfo& ProfileInfo::operator=(const ProfileInfo&) = default; ProfileInfo::~ProfileInfo() = default; ProfileInfo CreateProfileInfo(const std::string& name, bool is_incognito) { if (is_incognito) return {is_incognito, name, base::FilePath(), base::FilePath()}; CHECK(internal::IsValidNameForNonIncognitoProfile(name)); std::string dir_name = name; int suffix = 0; while (internal::IsProfileMarkedForDeletion(dir_name)) { suffix++; dir_name = name; dir_name.append(1, kSuffixDelimiter).append(base::NumberToString(suffix)); } base::FilePath data_path = GetDataPathFromDirName(dir_name); base::CreateDirectory(data_path); base::FilePath cache_path = data_path; #if BUILDFLAG(IS_POSIX) cache_path = GetCachePathFromDirName(dir_name); base::CreateDirectory(cache_path); #endif return {is_incognito, name, data_path, cache_path}; } base::FilePath ComputeBrowserPersisterDataBaseDir(const ProfileInfo& info) { base::FilePath base_path; if (info.is_incognito) { CHECK(base::PathService::Get(DIR_USER_DATA, &base_path)); if (info.name.empty()) { // Originally the Android side of WebLayer only supported a single // incognito file with an empty name. std::string file_name = "Incognito Restore Data"; base_path = base_path.AppendASCII(file_name); } else { std::string file_name = "Named Profile Incognito Restore Data"; base_path = base_path.AppendASCII(file_name).AppendASCII( base32::Base32Encode(info.name)); } } else { base_path = info.data_path.AppendASCII("Restore Data"); } return base_path; } void MarkProfileAsDeleted(const ProfileInfo& info) { if (info.is_incognito) return; base::FilePath data_root_path = GetProfileRootDataDir(); base::FilePath marker_path = GetProfileMarkerRootDataDir(); CHECK(data_root_path.AppendRelativePath(info.data_path, &marker_path)); base::File file(marker_path, base::File::FLAG_CREATE | base::File::FLAG_WRITE); if (!base::PathExists(marker_path)) { LOG(WARNING) << "Failure in deleting profile data. Profile:" << info.name << " error:" << static_cast(file.error_details()); } } void TryNukeProfileFromDisk(const ProfileInfo& info) { if (info.is_incognito) { // Incognito. Just delete session data. base::DeletePathRecursively(ComputeBrowserPersisterDataBaseDir(info)); return; } // This may fail, but that is ok since the marker is not deleted. base::DeletePathRecursively(info.data_path); #if BUILDFLAG(IS_POSIX) base::DeletePathRecursively(info.cache_path); #endif } std::vector ListProfileNames() { base::FilePath root_dir = GetProfileRootDataDir(); std::vector profile_names; base::FileEnumerator enumerator(root_dir, /*recursive=*/false, base::FileEnumerator::DIRECTORIES); for (base::FilePath path = enumerator.Next(); !path.empty(); path = enumerator.Next()) { std::string dir_name = enumerator.GetInfo().GetName().MaybeAsASCII(); std::string name = internal::CheckDirNameAndExtractName(dir_name); if (!name.empty() && !internal::IsProfileMarkedForDeletion(dir_name)) profile_names.push_back(name); } return profile_names; } void NukeProfilesMarkedForDeletion() { base::FilePath marker_root_dir = GetProfileMarkerRootDataDir(); base::FileEnumerator enumerator(marker_root_dir, /*recursive=*/false, base::FileEnumerator::FILES); for (base::FilePath marker_path = enumerator.Next(); !marker_path.empty(); marker_path = enumerator.Next()) { std::string dir_name = enumerator.GetInfo().GetName().MaybeAsASCII(); if (!internal::CheckDirNameAndExtractName(dir_name).empty()) { // Delete cache and data directory first before deleting marker. bool delete_success = true; #if BUILDFLAG(IS_POSIX) delete_success |= base::DeletePathRecursively(GetCachePathFromDirName(dir_name)); #endif // BUILDFLAG(IS_POSIX) delete_success |= base::DeletePathRecursively(GetDataPathFromDirName(dir_name)); // Only delete the marker if deletion is successful. if (delete_success) { base::DeleteFile(marker_path); } } } } namespace internal { bool IsValidNameForNonIncognitoProfile(const std::string& name) { for (char c : name) { if (!IsValidProfileNameChar(c)) return false; } return !name.empty(); } // If |dir_name| is valid, then return the |name|. Otherwise return the empty // string. std::string CheckDirNameAndExtractName(const std::string& dir_name) { std::vector parts = base::SplitString(dir_name, std::string(1, kSuffixDelimiter), base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); if (parts.size() == 0 || parts.size() > 2) return std::string(); if (!IsValidNameForNonIncognitoProfile(parts[0])) return std::string(); if (parts.size() > 1) { if (parts[1].empty()) return std::string(); for (char c : parts[1]) { if (!base::IsAsciiDigit(c)) return std::string(); } } return parts[0]; } bool IsProfileMarkedForDeletion(const std::string& dir_name) { base::FilePath marker = GetProfileMarkerRootDataDir().AppendASCII(dir_name.c_str()); return base::PathExists(marker); } } // namespace internal } // namespace weblayer