Home | History | Annotate | Download | only in cloud
      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 "chrome/browser/policy/cloud/resource_cache.h"
      6 
      7 #include "base/base64.h"
      8 #include "base/file_util.h"
      9 #include "base/files/file_enumerator.h"
     10 #include "base/logging.h"
     11 #include "base/strings/string_util.h"
     12 
     13 namespace policy {
     14 
     15 namespace {
     16 
     17 // Verifies that |value| is not empty and encodes it into base64url format,
     18 // which is safe to use as a file name on all platforms.
     19 bool Base64Encode(const std::string& value, std::string* encoded) {
     20   DCHECK(!value.empty());
     21   if (value.empty() || !base::Base64Encode(value, encoded))
     22     return false;
     23   ReplaceChars(*encoded, "+", "-", encoded);
     24   ReplaceChars(*encoded, "/", "_", encoded);
     25   return true;
     26 }
     27 
     28 // Decodes all elements of |input| from base64url format and stores the decoded
     29 // elements in |output|.
     30 bool Base64Encode(const std::set<std::string>& input,
     31                   std::set<std::string>* output) {
     32   output->clear();
     33   for (std::set<std::string>::const_iterator it = input.begin();
     34        it != input.end(); ++it) {
     35     std::string encoded;
     36     if (!Base64Encode(*it, &encoded)) {
     37       output->clear();
     38       return false;
     39     }
     40     output->insert(encoded);
     41   }
     42   return true;
     43 }
     44 
     45 // Decodes |encoded| from base64url format and verifies that the result is not
     46 // emtpy.
     47 bool Base64Decode(const std::string& encoded, std::string* value) {
     48   std::string buffer;
     49   ReplaceChars(encoded, "-", "+", &buffer);
     50   ReplaceChars(buffer, "_", "/", &buffer);
     51   return base::Base64Decode(buffer, value) && !value->empty();
     52 }
     53 
     54 }  // namespace
     55 
     56 ResourceCache::ResourceCache(const base::FilePath& cache_dir)
     57     : cache_dir_(cache_dir) {
     58   // Allow the cache to be created in a different thread than the thread that is
     59   // going to use it.
     60   DetachFromThread();
     61 }
     62 
     63 ResourceCache::~ResourceCache() {
     64   DCHECK(CalledOnValidThread());
     65 }
     66 
     67 bool ResourceCache::Store(const std::string& key,
     68                           const std::string& subkey,
     69                           const std::string& data) {
     70   DCHECK(CalledOnValidThread());
     71   base::FilePath subkey_path;
     72   // Delete the file before writing to it. This ensures that the write does not
     73   // follow a symlink planted at |subkey_path|, clobbering a file outside the
     74   // cache directory. The mechanism is meant to foil file-system-level attacks
     75   // where a symlink is planted in the cache directory before Chrome has
     76   // started. An attacker controlling a process running concurrently with Chrome
     77   // would be able to race against the protection by re-creating the symlink
     78   // between these two calls. There is nothing in file_util that could be used
     79   // to protect against such races, especially as the cache is cross-platform
     80   // and therefore cannot use any POSIX-only tricks.
     81   return VerifyKeyPathAndGetSubkeyPath(key, true, subkey, &subkey_path) &&
     82          base::DeleteFile(subkey_path, false) &&
     83          file_util::WriteFile(subkey_path, data.data(), data.size());
     84 }
     85 
     86 bool ResourceCache::Load(const std::string& key,
     87                          const std::string& subkey,
     88                          std::string* data) {
     89   DCHECK(CalledOnValidThread());
     90   base::FilePath subkey_path;
     91   // Only read from |subkey_path| if it is not a symlink.
     92   if (!VerifyKeyPathAndGetSubkeyPath(key, false, subkey, &subkey_path) ||
     93       file_util::IsLink(subkey_path)) {
     94     return false;
     95   }
     96   data->clear();
     97   return file_util::ReadFileToString(subkey_path, data);
     98 }
     99 
    100 void ResourceCache::LoadAllSubkeys(
    101     const std::string& key,
    102     std::map<std::string, std::string>* contents) {
    103   DCHECK(CalledOnValidThread());
    104   contents->clear();
    105   base::FilePath key_path;
    106   if (!VerifyKeyPath(key, false, &key_path))
    107     return;
    108 
    109   base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
    110   for (base::FilePath path = enumerator.Next(); !path.empty();
    111        path = enumerator.Next()) {
    112     const std::string encoded_subkey = path.BaseName().MaybeAsASCII();
    113     std::string subkey;
    114     std::string data;
    115     // Only read from |subkey_path| if it is not a symlink and its name is
    116     // a base64-encoded string.
    117     if (!file_util::IsLink(path) &&
    118         Base64Decode(encoded_subkey, &subkey) &&
    119         file_util::ReadFileToString(path, &data)) {
    120       (*contents)[subkey].swap(data);
    121     }
    122   }
    123 }
    124 
    125 void ResourceCache::Delete(const std::string& key, const std::string& subkey) {
    126   DCHECK(CalledOnValidThread());
    127   base::FilePath subkey_path;
    128   if (VerifyKeyPathAndGetSubkeyPath(key, false, subkey, &subkey_path))
    129     base::DeleteFile(subkey_path, false);
    130   // Delete() does nothing if the directory given to it is not empty. Hence, the
    131   // call below deletes the directory representing |key| if its last subkey was
    132   // just removed and does nothing otherwise.
    133   base::DeleteFile(subkey_path.DirName(), false);
    134 }
    135 
    136 void ResourceCache::PurgeOtherKeys(const std::set<std::string>& keys_to_keep) {
    137   DCHECK(CalledOnValidThread());
    138   std::set<std::string> encoded_keys_to_keep;
    139   if (!Base64Encode(keys_to_keep, &encoded_keys_to_keep))
    140     return;
    141 
    142   base::FileEnumerator enumerator(
    143       cache_dir_, false, base::FileEnumerator::DIRECTORIES);
    144   for (base::FilePath path = enumerator.Next(); !path.empty();
    145        path = enumerator.Next()) {
    146     const std::string name(path.BaseName().MaybeAsASCII());
    147     if (encoded_keys_to_keep.find(name) == encoded_keys_to_keep.end())
    148       base::DeleteFile(path, true);
    149   }
    150 }
    151 
    152 void ResourceCache::PurgeOtherSubkeys(
    153     const std::string& key,
    154     const std::set<std::string>& subkeys_to_keep) {
    155   DCHECK(CalledOnValidThread());
    156   base::FilePath key_path;
    157   if (!VerifyKeyPath(key, false, &key_path))
    158     return;
    159 
    160   std::set<std::string> encoded_subkeys_to_keep;
    161   if (!Base64Encode(subkeys_to_keep, &encoded_subkeys_to_keep))
    162     return;
    163 
    164   base::FileEnumerator enumerator(key_path, false, base::FileEnumerator::FILES);
    165   for (base::FilePath path = enumerator.Next(); !path.empty();
    166        path = enumerator.Next()) {
    167     const std::string name(path.BaseName().MaybeAsASCII());
    168     if (encoded_subkeys_to_keep.find(name) == encoded_subkeys_to_keep.end())
    169       base::DeleteFile(path, false);
    170   }
    171   // Delete() does nothing if the directory given to it is not empty. Hence, the
    172   // call below deletes the directory representing |key| if all of its subkeys
    173   // were just removed and does nothing otherwise.
    174   base::DeleteFile(key_path, false);
    175 }
    176 
    177 bool ResourceCache::VerifyKeyPath(const std::string& key,
    178                                   bool allow_create,
    179                                   base::FilePath* path) {
    180   std::string encoded;
    181   if (!Base64Encode(key, &encoded))
    182     return false;
    183   *path = cache_dir_.AppendASCII(encoded);
    184   return allow_create ? file_util::CreateDirectory(*path) :
    185                         base::DirectoryExists(*path);
    186 }
    187 
    188 bool ResourceCache::VerifyKeyPathAndGetSubkeyPath(const std::string& key,
    189                                                   bool allow_create_key,
    190                                                   const std::string& subkey,
    191                                                   base::FilePath* path) {
    192   base::FilePath key_path;
    193   std::string encoded;
    194   if (!VerifyKeyPath(key, allow_create_key, &key_path) ||
    195       !Base64Encode(subkey, &encoded)) {
    196     return false;
    197   }
    198   *path = key_path.AppendASCII(encoded);
    199   return true;
    200 }
    201 
    202 
    203 }  // namespace policy
    204