chromium/ios/chrome/browser/web/model/lookalike_url_app_interface.mm

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

#import "ios/chrome/browser/web/model/lookalike_url_app_interface.h"

#import "base/memory/raw_ptr.h"
#import "components/lookalikes/core/lookalike_url_util.h"
#import "ios/chrome/browser/web/model/lookalike_url_constants.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/chrome/test/app/tab_test_util.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_error.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_tab_allow_list.h"
#import "ios/web/public/navigation/web_state_policy_decider.h"
#import "ios/web/public/web_state_user_data.h"
#import "net/base/apple/url_conversions.h"

namespace {

// This decider determines whether a URL is a lookalike. If so, it cancels
// navigation and shows an error.
class LookalikeUrlDecider : public web::WebStatePolicyDecider,
                            public web::WebStateUserData<LookalikeUrlDecider> {
 public:
  LookalikeUrlDecider(web::WebState* web_state)
      : web::WebStatePolicyDecider(web_state), web_state_(web_state) {}

  LookalikeUrlDecider(const LookalikeUrlDecider&) = delete;
  LookalikeUrlDecider& operator=(const LookalikeUrlDecider&) = delete;

  void ShouldAllowResponse(
      NSURLResponse* response,
      web::WebStatePolicyDecider::ResponseInfo response_info,
      web::WebStatePolicyDecider::PolicyDecisionCallback callback) override {
    LookalikeUrlContainer* lookalike_container =
        LookalikeUrlContainer::FromWebState(web_state_);
    LookalikeUrlTabAllowList* allow_list =
        LookalikeUrlTabAllowList::FromWebState(web_state_);

    GURL response_url = net::GURLWithNSURL(response.URL);
    if (allow_list->IsDomainAllowed(response_url.host())) {
      return std::move(callback).Run(
          web::WebStatePolicyDecider::PolicyDecision::Allow());
    }
    if (response_url.path() == kLookalikePagePathForTesting) {
      GURL::Replacements safeReplacements;
      safeReplacements.SetPathStr("echo");
      if (@available(iOS 15.1, *)) {
      } else {
        // Workaround https://bugs.webkit.org/show_bug.cgi?id=226323, which
        // breaks some back/forward navigations between pages that share a
        // renderer process. Use 'localhost' instead of '127.0.0.1' for the
        // safe URL to prevent sharing renderer processes with unsafe URLs.
        safeReplacements.SetHostStr("localhost");
      }
      lookalike_container->SetLookalikeUrlInfo(
          response_url.ReplaceComponents(safeReplacements), response_url,
          lookalikes::LookalikeUrlMatchType::kSkeletonMatchTop5k);
      std::move(callback).Run(CreateLookalikeErrorDecision());
      return;
    }
    if (response_url.path() == kLookalikePageEmptyUrlPathForTesting) {
      lookalike_container->SetLookalikeUrlInfo(
          GURL(), response_url,
          lookalikes::LookalikeUrlMatchType::kSkeletonMatchTop5k);
      std::move(callback).Run(CreateLookalikeErrorDecision());
      return;
    }
    std::move(callback).Run(
        web::WebStatePolicyDecider::PolicyDecision::Allow());
  }

  WEB_STATE_USER_DATA_KEY_DECL();

 private:
  raw_ptr<web::WebState> web_state_ = nullptr;
};

WEB_STATE_USER_DATA_KEY_IMPL(LookalikeUrlDecider)

}  // namespace

@implementation LookalikeUrlAppInterface

+ (void)setUpLookalikeUrlDeciderForWebState {
  LookalikeUrlDecider::CreateForWebState(
      chrome_test_util::GetCurrentWebState());
}

+ (void)tearDownLookalikeUrlDeciderForWebState {
  LookalikeUrlDecider::RemoveFromWebState(
      chrome_test_util::GetCurrentWebState());
}

@end