chromium/chrome/browser/ash/file_system_provider/registry.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/file_system_provider/registry.h"

#include <optional>
#include <utility>

#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/values.h"
#include "chrome/browser/ash/file_system_provider/mount_path_util.h"
#include "chrome/browser/ash/file_system_provider/observer.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
#include "chrome/browser/ash/file_system_provider/service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "storage/browser/file_system/external_mount_points.h"

namespace ash::file_system_provider {

const char kPrefKeyFileSystemId[] = "file-system-id";
const char kPrefKeyDisplayName[] = "display-name";
const char kPrefKeyWritable[] = "writable";
const char kPrefKeySupportsNotifyTag[] = "supports-notify-tag";
const char kPrefKeyWatchers[] = "watchers";
const char kPrefKeyWatcherEntryPath[] = "entry-path";
const char kPrefKeyWatcherRecursive[] = "recursive";
const char kPrefKeyWatcherLastTag[] = "last-tag";
const char kPrefKeyWatcherPersistentOrigins[] = "persistent-origins";
const char kPrefKeyOpenedFilesLimit[] = "opened-files-limit";

void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterDictionaryPref(prefs::kFileSystemProviderMounted);
}

Registry::Registry(Profile* profile) : profile_(profile) {
}

Registry::~Registry() = default;

void Registry::RememberFileSystem(
    const ProvidedFileSystemInfo& file_system_info,
    const Watchers& watchers) {
  base::Value::Dict file_system;
  file_system.Set(kPrefKeyFileSystemId, file_system_info.file_system_id());
  file_system.Set(kPrefKeyDisplayName, file_system_info.display_name());
  file_system.Set(kPrefKeyWritable, file_system_info.writable());
  file_system.Set(kPrefKeySupportsNotifyTag,
                  file_system_info.supports_notify_tag());
  file_system.Set(kPrefKeyOpenedFilesLimit,
                  file_system_info.opened_files_limit());
  // We don't need to write and read "persistent" field (in MountOptions) to
  // and from preference because all filesystems which are remembered must be
  // persistent.

  base::Value::Dict watchers_dict;

  for (const auto& it : watchers) {
    base::Value::Dict watcher;
    watcher.Set(kPrefKeyWatcherEntryPath, it.second.entry_path.value());
    watcher.Set(kPrefKeyWatcherRecursive, it.second.recursive);
    watcher.Set(kPrefKeyWatcherLastTag, it.second.last_tag);
    base::Value::List persistent_origins_value;
    for (const auto& subscriber_it : it.second.subscribers) {
      // Only persistent subscribers should be stored in persistent storage.
      // Other ones should not be restired after a restart.
      if (subscriber_it.second.persistent) {
        persistent_origins_value.Append(subscriber_it.first.spec());
      }
    }
    watcher.Set(kPrefKeyWatcherPersistentOrigins,
                std::move(persistent_origins_value));
    watchers_dict.Set(it.second.entry_path.value(), std::move(watcher));
  }
  file_system.Set(kPrefKeyWatchers, std::move(watchers_dict));

  PrefService* const pref_service = profile_->GetPrefs();
  DCHECK(pref_service);

  ScopedDictPrefUpdate dict_update(pref_service,
                                   prefs::kFileSystemProviderMounted);

  base::Value::Dict* file_systems_per_extension =
      dict_update->EnsureDict(file_system_info.provider_id().ToString());
  file_systems_per_extension->Set(file_system_info.file_system_id(),
                                  std::move(file_system));
}

void Registry::ForgetFileSystem(const ProviderId& provider_id,
                                const std::string& file_system_id) {
  PrefService* const pref_service = profile_->GetPrefs();
  DCHECK(pref_service);

  ScopedDictPrefUpdate dict_update(pref_service,
                                   prefs::kFileSystemProviderMounted);

  base::Value::Dict* file_systems_per_extension =
      dict_update->FindDict(provider_id.ToString());
  if (!file_systems_per_extension)
    return;  // Nothing to forget.

  file_systems_per_extension->Remove(file_system_id);
  if (file_systems_per_extension->empty())
    dict_update->Remove(provider_id.ToString());
}

std::unique_ptr<Registry::RestoredFileSystems> Registry::RestoreFileSystems(
    const ProviderId& provider_id) {
  PrefService* const pref_service = profile_->GetPrefs();
  DCHECK(pref_service);

  const base::Value::Dict& file_systems =
      pref_service->GetDict(prefs::kFileSystemProviderMounted);

  const base::Value::Dict* file_systems_per_extension =
      file_systems.FindDict(provider_id.ToString());
  if (!file_systems_per_extension) {
    return base::WrapUnique(new RestoredFileSystems);  // Nothing to restore.
  }

  std::unique_ptr<RestoredFileSystems> restored_file_systems(
      new RestoredFileSystems);

  for (const auto file_system_it : *file_systems_per_extension) {
    if (!file_system_it.second.is_dict()) {
      LOG(ERROR)
          << "Malformed provided file system information in preferences.";
      continue;
    }

    const base::Value::Dict& file_system = file_system_it.second.GetDict();

    const std::string* file_system_id =
        file_system.FindString(kPrefKeyFileSystemId);
    const std::string* display_name =
        file_system.FindString(kPrefKeyDisplayName);
    std::optional<bool> writable = file_system.FindBool(kPrefKeyWritable);
    std::optional<bool> supports_notify_tag =
        file_system.FindBool(kPrefKeySupportsNotifyTag);
    std::optional<int> opened_files_limit =
        file_system.FindInt(kPrefKeyOpenedFilesLimit);

    // TODO(mtomasz): Move opened files limit to the mandatory list above in
    // M42.
    if ((!file_system_id || !display_name || !writable ||
         !supports_notify_tag || file_system_id->empty() ||
         display_name->empty()) ||
        // Optional fields.
        (opened_files_limit.has_value() && opened_files_limit.value() < 0)) {
      LOG(ERROR)
          << "Malformed provided file system information in preferences.";
      continue;
    }

    MountOptions options;
    options.file_system_id = *file_system_id;
    options.display_name = *display_name;
    options.writable = writable.value();
    options.supports_notify_tag = supports_notify_tag.value();
    options.opened_files_limit = opened_files_limit.value_or(0);

    RestoredFileSystem restored_file_system;
    restored_file_system.provider_id = provider_id;
    restored_file_system.options = options;

    // Restore watchers. It's optional, since this field is new.
    const base::Value::Dict* watchers = file_system.FindDict(kPrefKeyWatchers);
    if (watchers) {
      for (const auto watcher_it : *watchers) {
        if (!watcher_it.second.is_dict()) {
          LOG(ERROR) << "Malformed watcher information in preferences.";
          continue;
        }

        const base::Value::Dict& watcher = watcher_it.second.GetDict();

        const std::string* entry_path =
            watcher.FindString(kPrefKeyWatcherEntryPath);
        std::optional<bool> recursive =
            watcher.FindBool(kPrefKeyWatcherRecursive);
        const std::string* last_tag =
            watcher.FindString(kPrefKeyWatcherLastTag);
        const base::Value::List* persistent_origins =
            watcher.FindList(kPrefKeyWatcherPersistentOrigins);

        if (!entry_path || !recursive || !last_tag || !persistent_origins ||
            watcher_it.first != *entry_path || entry_path->empty() ||
            (!options.supports_notify_tag &&
             (!last_tag->empty() || persistent_origins->size()))) {
          LOG(ERROR) << "Malformed watcher information in preferences.";
          continue;
        }

        Watcher restored_watcher;
        restored_watcher.entry_path =
            base::FilePath::FromUTF8Unsafe(*entry_path);
        restored_watcher.recursive = recursive.value();
        restored_watcher.last_tag = *last_tag;
        for (const auto& persistent_origin : *persistent_origins) {
          if (!persistent_origin.is_string()) {
            LOG(ERROR) << "Malformed subscriber information in preferences.";
            continue;
          }
          const GURL origin_as_gurl(persistent_origin.GetString());
          restored_watcher.subscribers[origin_as_gurl].origin = origin_as_gurl;
          restored_watcher.subscribers[origin_as_gurl].persistent = true;
        }
        restored_file_system.watchers[WatcherKey(
            base::FilePath::FromUTF8Unsafe(*entry_path), recursive.value())] =
            restored_watcher;
      }
    }
    restored_file_systems->push_back(restored_file_system);
  }

  return restored_file_systems;
}

void Registry::UpdateWatcherTag(const ProvidedFileSystemInfo& file_system_info,
                                const Watcher& watcher) {
  PrefService* const pref_service = profile_->GetPrefs();
  DCHECK(pref_service);

  // TODO(mtomasz): Consider optimizing it by moving information about watchers
  // or even file systems to leveldb.
  ScopedDictPrefUpdate dict_update(pref_service,
                                   prefs::kFileSystemProviderMounted);

  // All of the following checks should not happen in healthy environment.
  // However, since they rely on storage, DCHECKs can't be used.
  base::Value::Dict* file_systems_per_extension =
      dict_update->FindDict(file_system_info.provider_id().ToString());
  base::Value::Dict* file_system = nullptr;
  base::Value::Dict* watchers = nullptr;
  base::Value::Dict* watcher_value = nullptr;

  if (file_systems_per_extension) {
    file_system =
        file_systems_per_extension->FindDict(file_system_info.file_system_id());
  }
  if (file_system)
    watchers = file_system->FindDict(kPrefKeyWatchers);
  if (watchers)
    watcher_value = watchers->FindDict(watcher.entry_path.value());

  if (!watcher_value) {
    // Broken preferences.
    LOG(ERROR) << "Broken preferences detected while updating a tag.";
    return;
  }

  watcher_value->Set(kPrefKeyWatcherLastTag, watcher.last_tag);
}

}  // namespace ash::file_system_provider