summaryrefslogtreecommitdiff
path: root/chromium/weblayer/browser/profile_disk_operations.cc
blob: f5f8e1b1160415f0e798846d47d0621b6a0f0214 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// 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
      // 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<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