// Copyright (c) 2011 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 "base/message_loop.h"
#include "base/threading/thread.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/io_thread.h"
#include "chrome/browser/net/ssl_config_service_manager.h"
#include "chrome/browser/prefs/pref_member.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/common/pref_names.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
#include "content/common/notification_type.h"
#include "net/base/ssl_config_service.h"

////////////////////////////////////////////////////////////////////////////////
//  SSLConfigServicePref

// An SSLConfigService which stores a cached version of the current SSLConfig
// prefs, which are updated by SSLConfigServiceManagerPref when the prefs
// change.
class SSLConfigServicePref : public net::SSLConfigService {
 public:
  SSLConfigServicePref() {}
  virtual ~SSLConfigServicePref() {}

  // Store SSL config settings in |config|. Must only be called from IO thread.
  virtual void GetSSLConfig(net::SSLConfig* config);

 private:
  // Allow the pref watcher to update our internal state.
  friend class SSLConfigServiceManagerPref;

  // This method is posted to the IO thread from the browser thread to carry the
  // new config information.
  void SetNewSSLConfig(const net::SSLConfig& new_config);

  // Cached value of prefs, should only be accessed from IO thread.
  net::SSLConfig cached_config_;

  DISALLOW_COPY_AND_ASSIGN(SSLConfigServicePref);
};

void SSLConfigServicePref::GetSSLConfig(net::SSLConfig* config) {
  *config = cached_config_;
}

void SSLConfigServicePref::SetNewSSLConfig(
    const net::SSLConfig& new_config) {
  net::SSLConfig orig_config = cached_config_;
  cached_config_ = new_config;
  ProcessConfigUpdate(orig_config, new_config);
}

////////////////////////////////////////////////////////////////////////////////
//  SSLConfigServiceManagerPref

// The manager for holding and updating an SSLConfigServicePref instance.
class SSLConfigServiceManagerPref
    : public SSLConfigServiceManager,
      public NotificationObserver {
 public:
  SSLConfigServiceManagerPref(PrefService* user_prefs,
                              PrefService* local_state);
  virtual ~SSLConfigServiceManagerPref() {}

  virtual net::SSLConfigService* Get();

 private:
  // Register user_prefs and local_state SSL preferences.
  static void RegisterPrefs(PrefService* prefs);

  // Copy pref values to local_state from user_prefs if local_state doesn't have
  // the pref value and user_prefs has the pref value. Remove them from
  // user_prefs.
  static void MigrateUserPrefs(PrefService* local_state,
                               PrefService* user_prefs);

  // Callback for preference changes.  This will post the changes to the IO
  // thread with SetNewSSLConfig.
  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details);

  // Store SSL config settings in |config|, directly from the preferences. Must
  // only be called from UI thread.
  void GetSSLConfigFromPrefs(net::SSLConfig* config);

  // The prefs (should only be accessed from UI thread)
  BooleanPrefMember rev_checking_enabled_;
  BooleanPrefMember ssl3_enabled_;
  BooleanPrefMember tls1_enabled_;

  scoped_refptr<SSLConfigServicePref> ssl_config_service_;

  DISALLOW_COPY_AND_ASSIGN(SSLConfigServiceManagerPref);
};

SSLConfigServiceManagerPref::SSLConfigServiceManagerPref(
    PrefService* user_prefs, PrefService* local_state)
    : ssl_config_service_(new SSLConfigServicePref()) {
  DCHECK(user_prefs);
  DCHECK(local_state);

  RegisterPrefs(user_prefs);
  RegisterPrefs(local_state);

  // TODO(rtenneti): remove migration code after 6 months.
  MigrateUserPrefs(local_state, user_prefs);

  rev_checking_enabled_.Init(prefs::kCertRevocationCheckingEnabled,
                             local_state, this);
  ssl3_enabled_.Init(prefs::kSSL3Enabled, local_state, this);
  tls1_enabled_.Init(prefs::kTLS1Enabled, local_state, this);

  // Initialize from UI thread.  This is okay as there shouldn't be anything on
  // the IO thread trying to access it yet.
  GetSSLConfigFromPrefs(&ssl_config_service_->cached_config_);
}

// static
void SSLConfigServiceManagerPref::RegisterPrefs(PrefService* prefs) {
  net::SSLConfig default_config;
  if (!prefs->FindPreference(prefs::kCertRevocationCheckingEnabled)) {
    prefs->RegisterBooleanPref(prefs::kCertRevocationCheckingEnabled,
                               default_config.rev_checking_enabled);
  }
  if (!prefs->FindPreference(prefs::kSSL3Enabled)) {
    prefs->RegisterBooleanPref(prefs::kSSL3Enabled,
                               default_config.ssl3_enabled);
  }
  if (!prefs->FindPreference(prefs::kTLS1Enabled)) {
    prefs->RegisterBooleanPref(prefs::kTLS1Enabled,
                               default_config.tls1_enabled);
  }
}

// static
void SSLConfigServiceManagerPref::MigrateUserPrefs(PrefService* local_state,
                                                   PrefService* user_prefs) {
  if (user_prefs->HasPrefPath(prefs::kCertRevocationCheckingEnabled)) {
    if (!local_state->HasPrefPath(prefs::kCertRevocationCheckingEnabled)) {
      // Migrate the kCertRevocationCheckingEnabled preference.
      local_state->SetBoolean(prefs::kCertRevocationCheckingEnabled,
          user_prefs->GetBoolean(prefs::kCertRevocationCheckingEnabled));
    }
    user_prefs->ClearPref(prefs::kCertRevocationCheckingEnabled);
  }
  if (user_prefs->HasPrefPath(prefs::kSSL3Enabled)) {
    if (!local_state->HasPrefPath(prefs::kSSL3Enabled)) {
      // Migrate the kSSL3Enabled preference.
      local_state->SetBoolean(prefs::kSSL3Enabled,
          user_prefs->GetBoolean(prefs::kSSL3Enabled));
    }
    user_prefs->ClearPref(prefs::kSSL3Enabled);
  }
  if (user_prefs->HasPrefPath(prefs::kTLS1Enabled)) {
    if (!local_state->HasPrefPath(prefs::kTLS1Enabled)) {
      // Migrate the kTLS1Enabled preference.
      local_state->SetBoolean(prefs::kTLS1Enabled,
          user_prefs->GetBoolean(prefs::kTLS1Enabled));
    }
    user_prefs->ClearPref(prefs::kTLS1Enabled);
  }
}

net::SSLConfigService* SSLConfigServiceManagerPref::Get() {
  return ssl_config_service_;
}

void SSLConfigServiceManagerPref::Observe(NotificationType type,
                                          const NotificationSource& source,
                                          const NotificationDetails& details) {
  base::Thread* io_thread = g_browser_process->io_thread();
  if (io_thread) {
    net::SSLConfig new_config;
    GetSSLConfigFromPrefs(&new_config);

    // Post a task to |io_loop| with the new configuration, so it can
    // update |cached_config_|.
    io_thread->message_loop()->PostTask(
        FROM_HERE,
        NewRunnableMethod(
            ssl_config_service_.get(),
            &SSLConfigServicePref::SetNewSSLConfig,
            new_config));
  }
}

void SSLConfigServiceManagerPref::GetSSLConfigFromPrefs(
    net::SSLConfig* config) {
  config->rev_checking_enabled = rev_checking_enabled_.GetValue();
  config->ssl3_enabled = ssl3_enabled_.GetValue();
  config->tls1_enabled = tls1_enabled_.GetValue();
  SSLConfigServicePref::SetSSLConfigFlags(config);
}

////////////////////////////////////////////////////////////////////////////////
//  SSLConfigServiceManager

// static
SSLConfigServiceManager* SSLConfigServiceManager::CreateDefaultManager(
    PrefService* user_prefs,
    PrefService* local_state) {
  return new SSLConfigServiceManagerPref(user_prefs, local_state);
}