| // 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 <name>.<number>, 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 defined(OS_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 // OS_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 defined(OS_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 |
| // incognitoofile 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<int>(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 defined(OS_POSIX) |
| base::DeletePathRecursively(info.cache_path); |
| #endif |
| } |
| |
| std::vector<std::string> ListProfileNames() { |
| base::FilePath root_dir = GetProfileRootDataDir(); |
| std::vector<std::string> 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 defined(OS_POSIX) |
| delete_success |= |
| base::DeletePathRecursively(GetCachePathFromDirName(dir_name)); |
| #endif // OS_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<std::string> 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 |