Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / components / policy / core / common / cloud / resource_cache.cc
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/policy/core/common/cloud/resource_cache.h"
6
7 #include "base/base64.h"
8 #include "base/callback.h"
9 #include "base/file_util.h"
10 #include "base/files/file_enumerator.h"
11 #include "base/logging.h"
12 #include "base/numerics/safe_conversions.h"
13 #include "base/sequenced_task_runner.h"
14 #include "base/strings/string_util.h"
15
16 namespace policy {
17
18 namespace {
19
20 // Verifies that |value| is not empty and encodes it into base64url format,
21 // which is safe to use as a file name on all platforms.
22 bool Base64Encode(const std::string& value, std::string* encoded) {
23   DCHECK(!value.empty());
24   if (value.empty())
25     return false;
26   base::Base64Encode(value, encoded);
27   base::ReplaceChars(*encoded, "+", "-", encoded);
28   base::ReplaceChars(*encoded, "/", "_", encoded);
29   return true;
30 }
31
32 // Decodes all elements of |input| from base64url format and stores the decoded
33 // elements in |output|.
34 bool Base64Encode(const std::set<std::string>& input,
35                   std::set<std::string>* output) {
36   output->clear();
37   for (std::set<std::string>::const_iterator it = input.begin();
38        it != input.end(); ++it) {
39     std::string encoded;
40     if (!Base64Encode(*it, &encoded)) {
41       output->clear();
42       return false;
43     }
44     output->insert(encoded);
45   }
46   return true;
47 }
48
49 // Decodes |encoded| from base64url format and verifies that the result is not
50 // emtpy.
51 bool Base64Decode(const std::string& encoded, std::string* value) {
52   std::string buffer;
53   base::ReplaceChars(encoded, "-", "+", &buffer);
54   base::ReplaceChars(buffer, "_", "/", &buffer);
55   return base::Base64Decode(buffer, value) && !value->empty();
56 }
57
58 }  // namespace
59
60 ResourceCache::ResourceCache(
61     const base::FilePath& cache_dir,
62     scoped_refptr<base::SequencedTaskRunner> task_runner)
63     : cache_dir_(cache_dir),
64       task_runner_(task_runner) {
65 }
66
67 ResourceCache::~ResourceCache() {
68   DCHECK(task_runner_->RunsTasksOnCurrentThread());
69 }
70
71 bool ResourceCache::Store(const std::string& key,
72                           const std::string& subkey,
73                           const std::string& data) {
74   DCHECK(task_runner_->RunsTasksOnCurrentThread());
75   base::FilePath subkey_path;
76   // Delete the file before writing to it. This ensures that the write does not
77   // follow a symlink planted at |subkey_path|, clobbering a file outside the
78   // cache directory. The mechanism is meant to foil file-system-level attacks
79   // where a symlink is planted in the cache directory before Chrome has
80   // started. An attacker controlling a process running concurrently with Chrome
81   // would be able to race against the protection by re-creating the symlink
82   // between these two calls. There is nothing in file_util that could be used
83   // to protect against such races, especially as the cache is cross-platform
84   // and therefore cannot use any POSIX-only tricks.
85   int size = base::checked_cast<int>(data.size());
86   return VerifyKeyPathAndGetSubkeyPath(key, true, subkey, &subkey_path) &&
87          base::DeleteFile(subkey_path, false) &&
88          (base::WriteFile(subkey_path, data.data(), size) == size);
89 }
90
91 bool ResourceCache::Load(const std::string& key,
92                          const std::string& subkey,
93                          std::string* data) {
94   DCHECK(task_runner_->RunsTasksOnCurrentThread());
95   base::FilePath subkey_path;
96   // Only read from |subkey_path| if it is not a symlink.
97   if (!VerifyKeyPathAndGetSubkeyPath(key, false, subkey, &subkey_path) ||
98       base::IsLink(subkey_path)) {
99     return false;
100   }
101   data->clear();
102   return base::ReadFileToString(subkey_path, data);
103 }
104
105 void ResourceCache::LoadAllSubkeys(
106     const std::string& key,
107     std::map<std::string, std::string>* contents) {
108   DCHECK(task_runner_->RunsTasksOnCurrentThread());
109   contents->clear();
110   base::FilePath key_path;
111   if (!VerifyKeyPath(key, false, &key_path))
112     return;
113
114   base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
115   for (base::FilePath path = enumerator.Next(); !path.empty();
116        path = enumerator.Next()) {
117     const std::string encoded_subkey = path.BaseName().MaybeAsASCII();
118     std::string subkey;
119     std::string data;
120     // Only read from |subkey_path| if it is not a symlink and its name is
121     // a base64-encoded string.
122     if (!base::IsLink(path) &&
123         Base64Decode(encoded_subkey, &subkey) &&
124         base::ReadFileToString(path, &data)) {
125       (*contents)[subkey].swap(data);
126     }
127   }
128 }
129
130 void ResourceCache::Delete(const std::string& key, const std::string& subkey) {
131   DCHECK(task_runner_->RunsTasksOnCurrentThread());
132   base::FilePath subkey_path;
133   if (VerifyKeyPathAndGetSubkeyPath(key, false, subkey, &subkey_path))
134     base::DeleteFile(subkey_path, false);
135   // Delete() does nothing if the directory given to it is not empty. Hence, the
136   // call below deletes the directory representing |key| if its last subkey was
137   // just removed and does nothing otherwise.
138   base::DeleteFile(subkey_path.DirName(), false);
139 }
140
141 void ResourceCache::Clear(const std::string& key) {
142   DCHECK(task_runner_->RunsTasksOnCurrentThread());
143   base::FilePath key_path;
144   if (VerifyKeyPath(key, false, &key_path))
145     base::DeleteFile(key_path, true);
146 }
147
148 void ResourceCache::FilterSubkeys(const std::string& key,
149                                   const SubkeyFilter& test) {
150   DCHECK(task_runner_->RunsTasksOnCurrentThread());
151
152   base::FilePath key_path;
153   if (!VerifyKeyPath(key, false, &key_path))
154     return;
155
156   base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
157   for (base::FilePath subkey_path = enumerator.Next();
158        !subkey_path.empty(); subkey_path = enumerator.Next()) {
159     std::string subkey;
160     // Delete files with invalid names, and files whose subkey doesn't pass the
161     // filter.
162     if (!Base64Decode(subkey_path.BaseName().MaybeAsASCII(), &subkey) ||
163         test.Run(subkey)) {
164       base::DeleteFile(subkey_path, true);
165     }
166   }
167
168   // Delete() does nothing if the directory given to it is not empty. Hence, the
169   // call below deletes the directory representing |key| if all of its subkeys
170   // were just removed and does nothing otherwise.
171   base::DeleteFile(key_path, false);
172 }
173
174 void ResourceCache::PurgeOtherKeys(const std::set<std::string>& keys_to_keep) {
175   DCHECK(task_runner_->RunsTasksOnCurrentThread());
176   std::set<std::string> encoded_keys_to_keep;
177   if (!Base64Encode(keys_to_keep, &encoded_keys_to_keep))
178     return;
179
180   base::FileEnumerator enumerator(
181       cache_dir_, false, base::FileEnumerator::DIRECTORIES);
182   for (base::FilePath path = enumerator.Next(); !path.empty();
183        path = enumerator.Next()) {
184     const std::string name(path.BaseName().MaybeAsASCII());
185     if (encoded_keys_to_keep.find(name) == encoded_keys_to_keep.end())
186       base::DeleteFile(path, true);
187   }
188 }
189
190 void ResourceCache::PurgeOtherSubkeys(
191     const std::string& key,
192     const std::set<std::string>& subkeys_to_keep) {
193   DCHECK(task_runner_->RunsTasksOnCurrentThread());
194   base::FilePath key_path;
195   if (!VerifyKeyPath(key, false, &key_path))
196     return;
197
198   std::set<std::string> encoded_subkeys_to_keep;
199   if (!Base64Encode(subkeys_to_keep, &encoded_subkeys_to_keep))
200     return;
201
202   base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
203   for (base::FilePath path = enumerator.Next(); !path.empty();
204        path = enumerator.Next()) {
205     const std::string name(path.BaseName().MaybeAsASCII());
206     if (encoded_subkeys_to_keep.find(name) == encoded_subkeys_to_keep.end())
207       base::DeleteFile(path, false);
208   }
209   // Delete() does nothing if the directory given to it is not empty. Hence, the
210   // call below deletes the directory representing |key| if all of its subkeys
211   // were just removed and does nothing otherwise.
212   base::DeleteFile(key_path, false);
213 }
214
215 bool ResourceCache::VerifyKeyPath(const std::string& key,
216                                   bool allow_create,
217                                   base::FilePath* path) {
218   std::string encoded;
219   if (!Base64Encode(key, &encoded))
220     return false;
221   *path = cache_dir_.AppendASCII(encoded);
222   return allow_create ? base::CreateDirectory(*path) :
223                         base::DirectoryExists(*path);
224 }
225
226 bool ResourceCache::VerifyKeyPathAndGetSubkeyPath(const std::string& key,
227                                                   bool allow_create_key,
228                                                   const std::string& subkey,
229                                                   base::FilePath* path) {
230   base::FilePath key_path;
231   std::string encoded;
232   if (!VerifyKeyPath(key, allow_create_key, &key_path) ||
233       !Base64Encode(subkey, &encoded)) {
234     return false;
235   }
236   *path = key_path.AppendASCII(encoded);
237   return true;
238 }
239
240
241 }  // namespace policy