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