chromium/ios/chrome/browser/app_launcher/model/app_launcher_abuse_detector.mm

// Copyright 2017 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/app_launcher/model/app_launcher_abuse_detector.h"

#import "base/strings/sys_string_conversions.h"
#import "ios/chrome/browser/app_launcher/model/app_launching_state.h"
#import "url/gurl.h"

const int kMaxAllowedConsecutiveExternalAppLaunches = 2;

@interface AppLauncherAbuseDetector ()
// Maps between external application redirection key and state.
// the key is a space separated combination of the absolute string for the
// original source URL, and the scheme of the external Application URL.
@property(nonatomic, strong)
    NSMutableDictionary<NSString*, AppLaunchingState*>* appLaunchingStates;
// Generates key for `appURL` and `sourceURL` to be used to retrieve state from
// `appLaunchingStates`.
+ (NSString*)stateKeyForAppURL:(const GURL&)appURL
                     sourceURL:(const GURL&)sourceURL;
@end

@implementation AppLauncherAbuseDetector

@synthesize appLaunchingStates = _appLaunchingStates;

+ (NSString*)stateKeyForAppURL:(const GURL&)appURL
                     sourceURL:(const GURL&)sourcePageURL {
  return
      [NSString stringWithFormat:@"%s %s", sourcePageURL.GetContent().c_str(),
                                 appURL.scheme().c_str()];
}

- (instancetype)init {
  self = [super init];
  if (self) {
    _appLaunchingStates = [[NSMutableDictionary alloc] init];
  }
  return self;
}

- (void)didRequestLaunchExternalAppURL:(const GURL&)URL
                     fromSourcePageURL:(const GURL&)sourcePageURL {
  NSString* key = [[self class] stateKeyForAppURL:URL sourceURL:sourcePageURL];
  if (!_appLaunchingStates[key]) {
    _appLaunchingStates[key] = [[AppLaunchingState alloc] init];
  }
  [_appLaunchingStates[key] updateWithLaunchRequest];
}

- (ExternalAppLaunchPolicy)launchPolicyForURL:(const GURL&)URL
                            fromSourcePageURL:(const GURL&)sourcePageURL {
  NSString* key = [[self class] stateKeyForAppURL:URL sourceURL:sourcePageURL];
  // Don't block apps that are not registered with the abuse detector.
  if (!_appLaunchingStates[key]) {
    return ExternalAppLaunchPolicyAllow;
  }

  AppLaunchingState* state = _appLaunchingStates[key];
  if ([state isAppLaunchingBlocked]) {
    return ExternalAppLaunchPolicyBlock;
  }

  if (state.consecutiveLaunchesCount >
      kMaxAllowedConsecutiveExternalAppLaunches) {
    return ExternalAppLaunchPolicyPrompt;
  }
  return ExternalAppLaunchPolicyAllow;
}

- (void)blockLaunchingAppURL:(const GURL&)URL
           fromSourcePageURL:(const GURL&)sourcePageURL {
  NSString* key = [[self class] stateKeyForAppURL:URL sourceURL:sourcePageURL];
  [_appLaunchingStates[key] setAppLaunchingBlocked:YES];
}
@end