// Copyright (c) 2012 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 "chrome/browser/protector/protected_prefs_watcher.h"

#include "base/base64.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/prefs/pref_set_observer.h"
#include "chrome/browser/prefs/scoped_user_pref_update.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/protector/histograms.h"
#include "chrome/browser/protector/protector_utils.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/notification_service.h"

using extensions::ExtensionPrefs;

namespace protector {

namespace {

// Prefix added to names of backup entries.
const char kBackupPrefsPrefix[] = "backup.";

// Names of prefs that are backed up.
const char* const kProtectedPrefNames[] = {
  prefs::kHomePage,
  prefs::kHomePageIsNewTabPage,
  prefs::kShowHomeButton,
  prefs::kRestoreOnStartup,
  prefs::kURLsToRestoreOnStartup,
  prefs::kPinnedTabs
};

// Backup pref names.
const char kBackupHomePage[] = "backup.homepage";
const char kBackupHomePageIsNewTabPage[] = "backup.homepage_is_newtabpage";
const char kBackupShowHomeButton[] = "backup.browser.show_home_button";
const char kBackupRestoreOnStartup[] = "backup.session.restore_on_startup";
const char kBackupURLsToRestoreOnStartup[] =
    "backup.session.urls_to_restore_on_startup";
const char kBackupPinnedTabs[] = "backup.pinned_tabs";

// Special backup entries.
const char kBackupExtensionsIDs[] = "backup.extensions.ids";
const char kBackupSignature[] = "backup._signature";
const char kBackupVersion[] = "backup._version";

// Returns name of the backup entry for pref |pref_name|.
std::string GetBackupNameFor(const std::string& pref_name) {
  return kBackupPrefsPrefix + pref_name;
}

// Appends a list of strings to |out|.
void StringAppendStringList(const base::ListValue* list, std::string* out) {
  for (base::ListValue::const_iterator it = list->begin(); it != list->end();
       ++it) {
    std::string item;
    if (!(*it)->GetAsString(&item))
      NOTREACHED();
    base::StringAppendF(out, "|%s", item.c_str());
  }
}

// Appends a dictionary with string values to |out|.
void StringAppendStringDictionary(const base::DictionaryValue* dict,
                                  std::string* out) {
  for (base::DictionaryValue::Iterator it(*dict); it.HasNext(); it.Advance()) {
    std::string value;
    if (!it.value().GetAsString(&value))
      NOTREACHED();
    base::StringAppendF(out, "|%s|%s", it.key().c_str(), value.c_str());
  }
}

void StringAppendBoolean(PrefService* prefs,
                         const char* path,
                         std::string* out) {
  if (prefs->HasPrefPath(path))
    base::StringAppendF(out, "|%d", prefs->GetBoolean(path) ? 1 : 0);
  else
    base::StringAppendF(out, "|");
}

void StringAppendInteger(PrefService* prefs,
                         const char* path,
                         std::string* out) {
  if (prefs->HasPrefPath(path))
    base::StringAppendF(out, "|%d", prefs->GetInteger(path));
  else
    base::StringAppendF(out, "|");
}

}  // namespace

// static
const int ProtectedPrefsWatcher::kCurrentVersionNumber = 4;

ProtectedPrefsWatcher::ProtectedPrefsWatcher(Profile* profile)
    : is_backup_valid_(true),
      profile_(profile) {
  // Perform necessary pref migrations before actually starting to observe
  // pref changes, otherwise the migration would affect the backup data as well.
  EnsurePrefsMigration();
  pref_observer_.reset(PrefSetObserver::CreateProtectedPrefSetObserver(
      profile->GetPrefs(), this));
  UpdateCachedPrefs();
  ValidateBackup();
  VLOG(1) << "Initialized pref watcher";
}

ProtectedPrefsWatcher::~ProtectedPrefsWatcher() {
}

// static
void ProtectedPrefsWatcher::RegisterUserPrefs(PrefService* prefs) {
  prefs->RegisterStringPref(kBackupHomePage, "",
                            PrefService::UNSYNCABLE_PREF);
  prefs->RegisterBooleanPref(kBackupHomePageIsNewTabPage, false,
                             PrefService::UNSYNCABLE_PREF);
  prefs->RegisterBooleanPref(kBackupShowHomeButton, false,
                             PrefService::UNSYNCABLE_PREF);
  prefs->RegisterIntegerPref(kBackupRestoreOnStartup, 0,
                             PrefService::UNSYNCABLE_PREF);
  prefs->RegisterListPref(kBackupURLsToRestoreOnStartup,
                          PrefService::UNSYNCABLE_PREF);
  prefs->RegisterListPref(kBackupPinnedTabs,
                          PrefService::UNSYNCABLE_PREF);
  prefs->RegisterListPref(kBackupExtensionsIDs,
                          PrefService::UNSYNCABLE_PREF);
  prefs->RegisterStringPref(kBackupSignature, "",
                            PrefService::UNSYNCABLE_PREF);
  prefs->RegisterIntegerPref(kBackupVersion, 1,
                             PrefService::UNSYNCABLE_PREF);
}

bool ProtectedPrefsWatcher::DidPrefChange(const std::string& path) const {
  std::string backup_path = GetBackupNameFor(path);
  PrefService* prefs = profile_->GetPrefs();
  const PrefService::Preference* new_pref = prefs->FindPreference(path.c_str());
  DCHECK(new_pref);
  const PrefService::Preference* backup_pref =
      profile_->GetPrefs()->FindPreference(backup_path.c_str());
  DCHECK(backup_pref);
  if (new_pref->IsDefaultValue())
    return !backup_pref->IsDefaultValue();
  if (!new_pref->IsUserControlled())
    return false;
  return !backup_pref->GetValue()->Equals(new_pref->GetValue());
}

const base::Value* ProtectedPrefsWatcher::GetBackupForPref(
    const std::string& path) const {
  if (!is_backup_valid_)
    return NULL;
  std::string backup_path = GetBackupNameFor(path);
  // These do not directly correspond to any real preference.
  DCHECK(backup_path != kBackupExtensionsIDs &&
         backup_path != kBackupSignature);
  PrefService* prefs = profile_->GetPrefs();
  // If backup is not set, return the default value of the actual pref.
  // TODO(ivankr): return NULL instead and handle appropriately in SettingChange
  // classes.
  if (!prefs->HasPrefPath(backup_path.c_str()))
    return prefs->GetDefaultPrefValue(path.c_str());
  const PrefService::Preference* backup_pref =
      profile_->GetPrefs()->FindPreference(backup_path.c_str());
  DCHECK(backup_pref);
  return backup_pref->GetValue();
}

void ProtectedPrefsWatcher::ForceUpdateBackup() {
  UMA_HISTOGRAM_ENUMERATION(
      kProtectorHistogramPrefs,
      kProtectorErrorForcedUpdate,
      kProtectorErrorCount);
  InitBackup();
}

void ProtectedPrefsWatcher::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK(type == chrome::NOTIFICATION_PREF_CHANGED);
  const std::string* pref_name = content::Details<std::string>(details).ptr();
  DCHECK(pref_name && pref_observer_->IsObserved(*pref_name));
  if (UpdateBackupEntry(*pref_name))
    UpdateBackupSignature();
}

void ProtectedPrefsWatcher::EnsurePrefsMigration() {
  SessionStartupPref::MigrateIfNecessary(profile_->GetPrefs());
}

bool ProtectedPrefsWatcher::UpdateCachedPrefs() {
  // ExtensionService may not yet have been initialized, so using static method
  // exposed for this purpose.
  ExtensionPrefs::ExtensionIds extension_ids =
      ExtensionPrefs::GetExtensionsFrom(profile_->GetPrefs());
  if (extension_ids == cached_extension_ids_)
    return false;
  cached_extension_ids_.swap(extension_ids);
  return true;
}

bool ProtectedPrefsWatcher::HasBackup() const {
  // TODO(ivankr): as soon as some irreversible change to Preferences happens,
  // add a condition that this change has occured as well (otherwise it's
  // possible to simply clear the "backup" dictionary to make settings
  // unprotected).
  return profile_->GetPrefs()->HasPrefPath(kBackupSignature);
}

void ProtectedPrefsWatcher::InitBackup() {
  PrefService* prefs = profile_->GetPrefs();
  for (size_t i = 0; i < arraysize(kProtectedPrefNames); ++i) {
    const base::Value* user_value =
        prefs->GetUserPrefValue(kProtectedPrefNames[i]);
    if (user_value)
      prefs->Set(GetBackupNameFor(kProtectedPrefNames[i]).c_str(), *user_value);
  }
  ListPrefUpdate extension_ids_update(prefs, kBackupExtensionsIDs);
  base::ListValue* extension_ids = extension_ids_update.Get();
  extension_ids->Clear();
  for (ExtensionPrefs::ExtensionIds::const_iterator it =
           cached_extension_ids_.begin();
       it != cached_extension_ids_.end(); ++it) {
    extension_ids->Append(base::Value::CreateStringValue(*it));
  }
  prefs->SetInteger(kBackupVersion, kCurrentVersionNumber);
  UpdateBackupSignature();
}

void ProtectedPrefsWatcher::MigrateOldBackupIfNeeded() {
  PrefService* prefs = profile_->GetPrefs();

  int current_version = prefs->GetInteger(kBackupVersion);
  VLOG(1) << "Backup version: " << current_version;
  if (current_version == kCurrentVersionNumber)
    return;

  switch (current_version) {
    case 1: {
        // Add pinned tabs.
        const base::Value* pinned_tabs =
            prefs->GetUserPrefValue(prefs::kPinnedTabs);
        if (pinned_tabs)
          prefs->Set(kBackupPinnedTabs, *pinned_tabs);
      }
      // FALL THROUGH

    case 2:
      // SessionStartupPref migration.
      DCHECK(prefs->GetBoolean(prefs::kRestoreOnStartupMigrated));
      prefs->SetInteger(kBackupRestoreOnStartup,
                        prefs->GetInteger(prefs::kRestoreOnStartup));
      prefs->Set(kBackupURLsToRestoreOnStartup,
                 *prefs->GetList(prefs::kURLsToRestoreOnStartup));
      // FALL THROUGH

    case 3:
      // Reset to default values backup prefs whose actual prefs are not set.
      for (size_t i = 0; i < arraysize(kProtectedPrefNames); ++i) {
        if (!prefs->HasPrefPath(kProtectedPrefNames[i]))
          prefs->ClearPref(GetBackupNameFor(kProtectedPrefNames[i]).c_str());
      }
      // FALL THROUGH
  }

  prefs->SetInteger(kBackupVersion, kCurrentVersionNumber);
  UpdateBackupSignature();
}

bool ProtectedPrefsWatcher::UpdateBackupEntry(const std::string& path) {
  std::string backup_path = GetBackupNameFor(path);
  PrefService* prefs = profile_->GetPrefs();
  const PrefService::Preference* pref = prefs->FindPreference(path.c_str());
  if (path == ExtensionPrefs::kExtensionsPref) {
    // For changes in extension dictionary, do nothing if the IDs list remained
    // the same.
    if (!UpdateCachedPrefs())
      return false;
    ListPrefUpdate extension_ids_update(prefs, kBackupExtensionsIDs);
    base::ListValue* extension_ids = extension_ids_update.Get();
    extension_ids->Clear();
    for (ExtensionPrefs::ExtensionIds::const_iterator it =
             cached_extension_ids_.begin();
         it != cached_extension_ids_.end(); ++it) {
      extension_ids->Append(base::Value::CreateStringValue(*it));
    }
  } else if (!prefs->HasPrefPath(path.c_str())) {
    // Preference has been removed, remove the backup as well.
    prefs->ClearPref(backup_path.c_str());
  } else if (!pref->IsUserControlled()) {
    return false;
  } else {
    prefs->Set(backup_path.c_str(), *pref->GetValue());
  }
  VLOG(1) << "Updated backup entry for: " << path;
  return true;
}

void ProtectedPrefsWatcher::UpdateBackupSignature() {
  PrefService* prefs = profile_->GetPrefs();
  std::string signed_data = GetSignatureData(prefs);
  DCHECK(!signed_data.empty());
  std::string signature = SignSetting(signed_data);
  DCHECK(!signature.empty());
  std::string signature_base64;
  if (!base::Base64Encode(signature, &signature_base64))
    NOTREACHED();
  prefs->SetString(kBackupSignature, signature_base64);
  // Schedule disk write on FILE thread as soon as possible.
  prefs->CommitPendingWrite();
  VLOG(1) << "Updated backup signature";
}

bool ProtectedPrefsWatcher::IsSignatureValid() const {
  DCHECK(HasBackup());
  PrefService* prefs = profile_->GetPrefs();
  std::string signed_data = GetSignatureData(prefs);
  DCHECK(!signed_data.empty());
  std::string signature;
  if (!base::Base64Decode(prefs->GetString(kBackupSignature), &signature))
    return false;
  return IsSettingValid(signed_data, signature);
}

void ProtectedPrefsWatcher::ValidateBackup() {
  if (!HasBackup()) {
    // Create initial backup entries and sign them.
    InitBackup();
    UMA_HISTOGRAM_ENUMERATION(
        kProtectorHistogramPrefs,
        kProtectorErrorValueValidZero,
        kProtectorErrorCount);
  } else if (IsSignatureValid()) {
    MigrateOldBackupIfNeeded();
    UMA_HISTOGRAM_ENUMERATION(
        kProtectorHistogramPrefs,
        kProtectorErrorValueValid,
        kProtectorErrorCount);
  } else {
    LOG(WARNING) << "Invalid backup signature";
    is_backup_valid_ = false;
    // The whole backup has been compromised, overwrite it.
    InitBackup();
    UMA_HISTOGRAM_ENUMERATION(
        kProtectorHistogramPrefs,
        kProtectorErrorBackupInvalid,
        kProtectorErrorCount);
  }
}

std::string ProtectedPrefsWatcher::GetSignatureData(PrefService* prefs) const {
  int current_version = prefs->GetInteger(kBackupVersion);
  // TODO(ivankr): replace this with some existing reliable serializer.
  // JSONWriter isn't a good choice because JSON formatting may change suddenly.
  std::string data = prefs->GetString(kBackupHomePage);
  StringAppendBoolean(prefs, kBackupHomePageIsNewTabPage, &data);
  StringAppendBoolean(prefs, kBackupShowHomeButton, &data);
  StringAppendInteger(prefs, kBackupRestoreOnStartup, &data);
  StringAppendStringList(prefs->GetList(kBackupURLsToRestoreOnStartup), &data);
  StringAppendStringList(prefs->GetList(kBackupExtensionsIDs), &data);
  if (current_version >= 2) {
    // Version itself is included only since version 2 since it wasn't there
    // in version 1.
    base::StringAppendF(&data, "|v%d", current_version);
    const base::ListValue* pinned_tabs = prefs->GetList(kBackupPinnedTabs);
    for (base::ListValue::const_iterator it = pinned_tabs->begin();
         it != pinned_tabs->end(); ++it) {
      const base::DictionaryValue* tab = NULL;
      if (!(*it)->GetAsDictionary(&tab)) {
        NOTREACHED();
        continue;
      }
      StringAppendStringDictionary(tab, &data);
    }
  }
  return data;
}

}  // namespace protector