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

#import <memory>
#import <string>

#import "base/base_paths.h"
#import "base/functional/bind.h"
#import "base/path_service.h"
#import "base/strings/escape.h"
#import "base/strings/string_util.h"
#import "base/strings/stringprintf.h"
#import "base/test/ios/wait_util.h"
#import "components/policy/policy_constants.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/overlays/model/public/web_content_area/alert_constants.h"
#import "ios/chrome/browser/policy/model/policy_earl_grey_utils.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/ssl/model/insecure_form_warning_app_interface.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/components/security_interstitials/https_only_mode/feature.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ios/testing/embedded_test_server_handlers.h"
#import "net/test/embedded_test_server/default_handlers.h"
#import "net/test/embedded_test_server/http_request.h"
#import "net/test/embedded_test_server/http_response.h"
#import "net/test/embedded_test_server/request_handler_util.h"
#import "ui/base/l10n/l10n_util.h"

using base::test::ios::kWaitForUIElementTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;

void TapCancel() {
  [[EarlGrey selectElementWithMatcher:chrome_test_util::CancelButton()]
      performAction:grey_tap()];
}

void TapSubmitAnyway() {
  id<GREYMatcher> button = chrome_test_util::ButtonWithAccessibilityLabelId(
      IDS_INSECURE_FORM_SUBMIT_BUTTON);
  [[EarlGrey selectElementWithMatcher:button] performAction:grey_tap()];
}

void WaitForInsecureFormDialog() {
  ConditionBlock condition = ^{
    NSError* error = nil;
    id<GREYMatcher> dialog_matcher =
        grey_accessibilityID(kInsecureFormWarningAccessibilityIdentifier);
    [[EarlGrey selectElementWithMatcher:dialog_matcher]
        assertWithMatcher:grey_notNil()
                    error:&error];
    return !error;
  };
  GREYAssert(WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, condition),
             @"Insecure form warning was not shown.");
}

// Tests submission of insecure forms with POST data.
@interface InsecureFormTestCase : ChromeTestCase {
  // This server serves over HTTPS, but its port is treated as a secure URL.
  std::unique_ptr<net::test_server::EmbeddedTestServer> _fakeHTTPSServer;
}
@end

@implementation InsecureFormTestCase

- (void)setUp {
  [super setUp];
  _fakeHTTPSServer = std::make_unique<net::test_server::EmbeddedTestServer>(
      net::test_server::EmbeddedTestServer::TYPE_HTTP);
  _fakeHTTPSServer->ServeFilesFromDirectory(
      base::PathService::CheckedGet(base::DIR_ASSETS)
          .AppendASCII("ios/testing/data/http_server_files/"));

  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
  GREYAssertTrue(_fakeHTTPSServer->Start(),
                 @"Test HTTPS server failed to start.");

  // Set the HTTP server's URL as insecure. We need this so that the tests don't
  // treat 127.0.0.1:port as secure.
  [InsecureFormWarningAppInterface
      setInsecureFormPortsForTesting:self->_fakeHTTPSServer->port()
               portTreatedAsInsecure:self.testServer->port()];
}

- (void)tearDownHelper {
  [InsecureFormWarningAppInterface setInsecureFormPortsForTesting:0
                                            portTreatedAsInsecure:0];
  policy_test_utils::ClearPolicies();

  [super tearDownHelper];
}

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config;
  config.relaunch_policy = NoForceRelaunchAndResetState;
  if ([self isRunningTest:@selector(testInsecureFormSubmit_FeatureDisabled)]) {
    config.features_disabled.push_back(
        security_interstitials::features::kInsecureFormSubmissionInterstitial);
  } else {
    config.features_enabled.push_back(
        security_interstitials::features::kInsecureFormSubmissionInterstitial);
  }
  return config;
}

- (void)submitFormAndExpectNoWarning:(const GURL&)pageURL {
  [ChromeEarlGrey loadURL:pageURL];
  [ChromeEarlGrey
      waitForWebStateContainingText:"Insecure form ready to submit"];

  // Submit the form, should go through without a warning.
  [ChromeEarlGrey tapWebStateElementWithID:@"submit"];
  [ChromeEarlGrey
      waitForWebStateContainingText:"username=testuser&password=testpassword"];
}

#pragma mark - Tests

// Tests that posting from HTTP to HTTP doesn't show a warning.
- (void)testHttpToHttpFormSubmit {
  std::string URLString =
      "/insecure_form.html?" + self.testServer->GetURL("/echo").spec();
  const GURL HTTPToHTTPFormURL = self.testServer->GetURL(URLString);
  [self submitFormAndExpectNoWarning:HTTPToHTTPFormURL];
}

// Tests that posting from HTTPS to HTTP shows a warning and tapping cancel
// doesn't send post data.
- (void)testInsecureFormSubmit_Cancel {
  std::string URLString =
      "/insecure_form.html?" + self.testServer->GetURL("/echo").spec();
  const GURL FakeHTTPSToHTTPFormURL = _fakeHTTPSServer->GetURL(URLString);

  [ChromeEarlGrey loadURL:FakeHTTPSToHTTPFormURL];
  [ChromeEarlGrey
      waitForWebStateContainingText:"Insecure form ready to submit"];

  // Submit the form. The insecure form warning should block the
  // submission.
  [ChromeEarlGrey tapWebStateElementWithID:@"submit"];
  WaitForInsecureFormDialog();
  TapCancel();
  [ChromeEarlGrey
      waitForWebStateContainingText:"Insecure form ready to submit"];
}

// Tests that posting from HTTPS to HTTP shows a warning and tapping
// "Send anyway" sends the post data.
- (void)testInsecureFormSubmit_SendAnyway {
  std::string URLString =
      "/insecure_form.html?" + self.testServer->GetURL("/echo").spec();
  const GURL FakeHTTPSToHTTPFormURL = _fakeHTTPSServer->GetURL(URLString);

  [ChromeEarlGrey loadURL:FakeHTTPSToHTTPFormURL];
  [ChromeEarlGrey
      waitForWebStateContainingText:"Insecure form ready to submit"];

  // Submit the form. The insecure form warning should block the
  // submission.
  [ChromeEarlGrey tapWebStateElementWithID:@"submit"];
  WaitForInsecureFormDialog();
  TapSubmitAnyway();
  [ChromeEarlGrey
      waitForWebStateContainingText:"username=testuser&password=testpassword"];
}

// Tests that posting from HTTPS to HTTP doesn't show a warning when the feature
// is disabled.
- (void)testInsecureFormSubmit_FeatureDisabled {
  std::string URLString =
      "/insecure_form.html?" + self.testServer->GetURL("/echo").spec();
  const GURL FakeHTTPSToHTTPFormURL = _fakeHTTPSServer->GetURL(URLString);

  [self submitFormAndExpectNoWarning:FakeHTTPSToHTTPFormURL];
}

// Tests that disabling the feature by policy will stop showing a warning.
- (void)testInsecureFormSubmit_DisabledByPolicy {
  policy_test_utils::SetPolicy(false,
                               policy::key::kInsecureFormsWarningsEnabled);

  std::string URLString =
      "/insecure_form.html?" + self.testServer->GetURL("/echo").spec();
  const GURL FakeHTTPSToHTTPFormURL = _fakeHTTPSServer->GetURL(URLString);

  [self submitFormAndExpectNoWarning:FakeHTTPSToHTTPFormURL];
}

@end
