// Copyright 2014 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/extensions/extension_install_checker.h"

#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/blacklist.h"
#include "chrome/browser/extensions/chrome_requirements_checker.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/management_policy.h"

namespace extensions {

ExtensionInstallChecker::ExtensionInstallChecker(Profile* profile)
    : profile_(profile),
      blacklist_state_(NOT_BLACKLISTED),
      policy_allows_load_(true),
      current_sequence_number_(0),
      running_checks_(0),
      fail_fast_(false),
      weak_ptr_factory_(this) {
}

ExtensionInstallChecker::~ExtensionInstallChecker() {
}

void ExtensionInstallChecker::Start(int enabled_checks,
                                    bool fail_fast,
                                    const Callback& callback) {
  // Profile is null in tests.
  if (profile_) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    if (!extension_.get()) {
      NOTREACHED();
      return;
    }
  }

  if (is_running() || !enabled_checks || callback.is_null()) {
    NOTREACHED();
    return;
  }

  running_checks_ = enabled_checks;
  fail_fast_ = fail_fast;
  callback_ = callback;
  ResetResults();

  // Execute the management policy check first as it is synchronous.
  if (enabled_checks & CHECK_MANAGEMENT_POLICY) {
    CheckManagementPolicy();
    if (!is_running())
      return;
  }

  if (enabled_checks & CHECK_REQUIREMENTS) {
    CheckRequirements();
    if (!is_running())
      return;
  }

  if (enabled_checks & CHECK_BLACKLIST)
    CheckBlacklistState();
}

void ExtensionInstallChecker::CheckManagementPolicy() {
  DCHECK(extension_.get());

  base::string16 error;
  bool allow = ExtensionSystem::Get(profile_)->management_policy()->UserMayLoad(
      extension_.get(), &error);
  OnManagementPolicyCheckDone(allow, base::UTF16ToUTF8(error));
}

void ExtensionInstallChecker::OnManagementPolicyCheckDone(
    bool allows_load,
    const std::string& error) {
  policy_allows_load_ = allows_load;
  policy_error_ = error;
  DCHECK(policy_allows_load_ || !policy_error_.empty());

  running_checks_ &= ~CHECK_MANAGEMENT_POLICY;
  MaybeInvokeCallback();
}

void ExtensionInstallChecker::CheckRequirements() {
  DCHECK(extension_.get());

  if (!requirements_checker_.get())
    requirements_checker_.reset(new ChromeRequirementsChecker());
  requirements_checker_->Check(
      extension_,
      base::Bind(&ExtensionInstallChecker::OnRequirementsCheckDone,
                 weak_ptr_factory_.GetWeakPtr(),
                 current_sequence_number_));
}

void ExtensionInstallChecker::OnRequirementsCheckDone(
    int sequence_number,
    const std::vector<std::string>& errors) {
  // Some pending results may arrive after fail fast.
  if (sequence_number != current_sequence_number_)
    return;

  requirement_errors_ = errors;

  running_checks_ &= ~CHECK_REQUIREMENTS;
  MaybeInvokeCallback();
}

void ExtensionInstallChecker::CheckBlacklistState() {
  DCHECK(extension_.get());

  extensions::Blacklist* blacklist = Blacklist::Get(profile_);
  blacklist->IsBlacklisted(
      extension_->id(),
      base::Bind(&ExtensionInstallChecker::OnBlacklistStateCheckDone,
                 weak_ptr_factory_.GetWeakPtr(),
                 current_sequence_number_));
}

void ExtensionInstallChecker::OnBlacklistStateCheckDone(int sequence_number,
                                                        BlacklistState state) {
  // Some pending results may arrive after fail fast.
  if (sequence_number != current_sequence_number_)
    return;

  blacklist_state_ = state;

  running_checks_ &= ~CHECK_BLACKLIST;
  MaybeInvokeCallback();
}

void ExtensionInstallChecker::ResetResults() {
  requirement_errors_.clear();
  blacklist_state_ = NOT_BLACKLISTED;
  policy_allows_load_ = true;
  policy_error_.clear();
}

void ExtensionInstallChecker::MaybeInvokeCallback() {
  if (callback_.is_null())
    return;

  // Set bits for failed checks.
  int failed_mask = 0;
  if (blacklist_state_ == BLACKLISTED_MALWARE)
    failed_mask |= CHECK_BLACKLIST;
  if (!requirement_errors_.empty())
    failed_mask |= CHECK_REQUIREMENTS;
  if (!policy_allows_load_)
    failed_mask |= CHECK_MANAGEMENT_POLICY;

  // Invoke callback if all checks are complete or there was at least one
  // failure and |fail_fast_| is true.
  if (!is_running() || (failed_mask && fail_fast_)) {
    // If we are failing fast, discard any pending results.
    weak_ptr_factory_.InvalidateWeakPtrs();
    running_checks_ = 0;
    ++current_sequence_number_;

    Callback callback_copy = callback_;
    callback_.Reset();

    // This instance may be owned by the callback recipient and deleted here,
    // so reset |callback_| first and invoke a copy of the callback.
    callback_copy.Run(failed_mask);
  }
}

}  // namespace extensions