// Copyright 2015 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/ssl/certificate_reporting_test_utils.h"

#include <utility>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/macros.h"
#include "base/metrics/field_trial.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/ping_manager.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/safe_browsing/ui_manager.h"
#include "chrome/browser/ssl/cert_report_helper.h"
#include "chrome/browser/ssl/ssl_cert_reporter.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/pref_names.h"
#include "components/certificate_reporting/error_report.h"
#include "components/certificate_reporting/error_reporter.h"
#include "components/prefs/pref_service.h"
#include "components/variations/variations_associated_data.h"
#include "net/url_request/certificate_report_sender.h"
#include "net/url_request/url_request_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using safe_browsing::SafeBrowsingService;
using safe_browsing::SafeBrowsingUIManager;

namespace {

void SetMockReporter(
    SafeBrowsingService* safe_browsing_service,
    scoped_ptr<certificate_reporting::ErrorReporter> reporter) {
  safe_browsing_service->ping_manager()->SetCertificateErrorReporterForTesting(
      std::move(reporter));
}

// This is a test implementation of the interface that blocking pages
// use to send certificate reports. It checks that the blocking page
// calls or does not call the report method when a report should or
// should not be sent, respectively.
class MockSSLCertReporter : public SSLCertReporter {
 public:
  MockSSLCertReporter(
      const scoped_refptr<SafeBrowsingUIManager>& safe_browsing_ui_manager,
      const base::Closure& report_sent_callback)
      : safe_browsing_ui_manager_(safe_browsing_ui_manager),
        reported_(false),
        expect_report_(false),
        report_sent_callback_(report_sent_callback) {}

  ~MockSSLCertReporter() override { EXPECT_EQ(expect_report_, reported_); }

  // SSLCertReporter implementation.
  void ReportInvalidCertificateChain(
      const std::string& serialized_report) override {
    reported_ = true;
    if (expect_report_) {
      safe_browsing_ui_manager_->ReportInvalidCertificateChain(
          serialized_report, report_sent_callback_);
    }
  }

  void set_expect_report(bool expect_report) { expect_report_ = expect_report; }

 private:
  const scoped_refptr<SafeBrowsingUIManager> safe_browsing_ui_manager_;
  bool reported_;
  bool expect_report_;
  base::Closure report_sent_callback_;
};

}  // namespace

namespace certificate_reporting_test_utils {

// This class is used to test invalid certificate chain reporting when
// the user opts in to do so on the interstitial. It keeps track of the
// most recent hostname for which an extended reporting report would
// have been sent over the network.
class CertificateReportingTest::MockReporter
    : public certificate_reporting::ErrorReporter {
 public:
  MockReporter(
      net::URLRequestContext* request_context,
      const GURL& upload_url,
      net::CertificateReportSender::CookiesPreference cookies_preference);

  // ErrorReporter implementation.
  void SendExtendedReportingReport(
      const std::string& serialized_report) override;

  // Returns the hostname in the report for the last call to
  // |SendReport|.
  const std::string& latest_hostname_reported() {
    return latest_hostname_reported_;
  }

 private:
  std::string latest_hostname_reported_;

  DISALLOW_COPY_AND_ASSIGN(MockReporter);
};

CertificateReportingTest::MockReporter::MockReporter(
    net::URLRequestContext* request_context,
    const GURL& upload_url,
    net::CertificateReportSender::CookiesPreference cookies_preference)
    : certificate_reporting::ErrorReporter(request_context,
                                           upload_url,
                                           cookies_preference) {}

void CertificateReportingTest::MockReporter::SendExtendedReportingReport(
    const std::string& serialized_report) {
  certificate_reporting::ErrorReport report;
  ASSERT_TRUE(report.InitializeFromString(serialized_report));
  latest_hostname_reported_ = report.hostname();
}

void CertificateReportingTest::SetUpMockReporter() {
  // Set up the mock reporter to track the hostnames that reports get
  // sent for. The request_context argument is null here
  // because the MockReporter doesn't actually use a
  // request_context. (In order to pass a real request_context, the
  // reporter would have to be constructed on the IO thread.)
  reporter_ = new CertificateReportingTest::MockReporter(
      nullptr, GURL("http://example.test"),
      net::CertificateReportSender::DO_NOT_SEND_COOKIES);

  scoped_refptr<SafeBrowsingService> safe_browsing_service =
      g_browser_process->safe_browsing_service();
  ASSERT_TRUE(safe_browsing_service);

  content::BrowserThread::PostTask(
      content::BrowserThread::IO, FROM_HERE,
      base::Bind(SetMockReporter, safe_browsing_service,
                 base::Passed(scoped_ptr<certificate_reporting::ErrorReporter>(
                     reporter_))));
}

const std::string& CertificateReportingTest::GetLatestHostnameReported() const {
  return reporter_->latest_hostname_reported();
}

void SetCertReportingOptIn(Browser* browser, OptIn opt_in) {
  browser->profile()->GetPrefs()->SetBoolean(
      prefs::kSafeBrowsingExtendedReportingEnabled,
      opt_in == EXTENDED_REPORTING_OPT_IN);
}

scoped_ptr<SSLCertReporter> SetUpMockSSLCertReporter(
    base::RunLoop* run_loop,
    ExpectReport expect_report) {
  // Set up a MockSSLCertReporter to keep track of when the blocking
  // page invokes the cert reporter.
  SafeBrowsingService* sb_service = g_browser_process->safe_browsing_service();
  EXPECT_TRUE(sb_service);
  if (!sb_service)
    return nullptr;

  scoped_ptr<MockSSLCertReporter> ssl_cert_reporter(new MockSSLCertReporter(
      sb_service->ui_manager(), expect_report == CERT_REPORT_EXPECTED
                                    ? run_loop->QuitClosure()
                                    : base::Bind(&base::DoNothing)));
  ssl_cert_reporter->set_expect_report(expect_report == CERT_REPORT_EXPECTED);
  return std::move(ssl_cert_reporter);
}

ExpectReport GetReportExpectedFromFinch() {
  const std::string group_name = base::FieldTrialList::FindFullName(
      CertReportHelper::kFinchExperimentName);

  if (group_name == CertReportHelper::kFinchGroupShowPossiblySend) {
    const std::string param = variations::GetVariationParamValue(
        CertReportHelper::kFinchExperimentName,
        CertReportHelper::kFinchParamName);
    double sendingThreshold;
    if (!base::StringToDouble(param, &sendingThreshold))
      return CERT_REPORT_NOT_EXPECTED;

    if (sendingThreshold == 1.0)
      return certificate_reporting_test_utils::CERT_REPORT_EXPECTED;
  }
  return certificate_reporting_test_utils::CERT_REPORT_NOT_EXPECTED;
}

}  // namespace certificate_reporting_test_utils