910e62b5创建于 1月15日历史提交
// Copyright 2021 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/components/credential_provider_extension/password_spec_fetcher.h"

#import <string_view>

#import "base/base64.h"
#import "components/autofill/core/browser/proto/password_requirements.pb.h"

using autofill::DomainSuggestions;
using autofill::PasswordRequirementsSpec;

namespace {

// URL of the fetching endpoint.
NSString* const kPasswordSpecURL =
    @"https://content-autofill.googleapis.com/v1/domainSuggestions/";
// Header field name for the API key.
NSString* const kApiKeyHeaderField = @"X-Goog-Api-Key";
// Encoding requested from the server.
NSString* const kEncodeKeyValue = @"base64";
// Header field name for the encoding.
NSString* const kEncodeKeyHeaderField = @"X-Goog-Encode-Response-If-Executable";
// Query parameter name to for the type of response.
NSString* const kAltQueryName = @"alt";
// Query parameter value for a bits response (compared to a JSON response).
NSString* const kAltQueryValue = @"proto";
// Timeout for the spec fetching request.
const NSTimeInterval kPasswordSpecTimeout = 10;

}  // namespace

@interface PasswordSpecFetcher ()

// Host that identifies the spec to be fetched.
@property(nonatomic, copy) NSString* host;

// API key to query the spec.
@property(nonatomic, copy) NSString* APIKey;

// Data task for fetching the spec.
@property(nonatomic, copy) NSURLSessionDataTask* task;

// Completion to be called once there is a response.
@property(nonatomic, copy) FetchSpecCompletionBlock completion;

// The spec if ready or an empty one if fetch hasn't happened.
@property(nonatomic, readwrite) PasswordRequirementsSpec spec;

@end

@implementation PasswordSpecFetcher

- (instancetype)initWithHost:(NSString*)host APIKey:(NSString*)APIKey {
  self = [super init];
  if (self) {
    // Replace a nil host with the empty string.
    if (!host) {
      host = @"";
    }
    _host = [host stringByAddingPercentEncodingWithAllowedCharacters:
                      NSCharacterSet.URLQueryAllowedCharacterSet];
    _APIKey = APIKey;
  }
  return self;
}

- (BOOL)didFetchSpec {
  return self.task.state == NSURLSessionTaskStateCompleted;
}

- (void)fetchSpecWithCompletion:(FetchSpecCompletionBlock)completion {
  self.completion = completion;

  if (self.task) {
    return;
  }
  NSString* finalURL = [kPasswordSpecURL stringByAppendingString:self.host];
  NSURLComponents* URLComponents =
      [NSURLComponents componentsWithString:finalURL];
  NSURLQueryItem* queryAltItem =
      [NSURLQueryItem queryItemWithName:kAltQueryName value:kAltQueryValue];
  URLComponents.queryItems = @[ queryAltItem ];
  NSMutableURLRequest* request =
      [NSMutableURLRequest requestWithURL:URLComponents.URL
                              cachePolicy:NSURLRequestUseProtocolCachePolicy
                          timeoutInterval:kPasswordSpecTimeout];
  [request setValue:self.APIKey forHTTPHeaderField:kApiKeyHeaderField];
  [request setValue:kEncodeKeyValue forHTTPHeaderField:kEncodeKeyHeaderField];

  __weak __typeof__(self) weakSelf = self;
  NSURLSession* session = [NSURLSession sharedSession];
  self.task =
      [session dataTaskWithRequest:request
                 completionHandler:^(NSData* data, NSURLResponse* response,
                                     NSError* error) {
                   [weakSelf onReceivedData:data response:response error:error];
                 }];
  [self.task resume];
}

- (void)onReceivedData:(NSData*)data
              response:(NSURLResponse*)response
                 error:(NSError*)error {
  // Return early if there is an error.
  if (error) {
    [self executeCompletion];
    return;
  }

  // Parse the proto and execute completion.
  std::string decoded;
  const std::string_view encoded_bytes(static_cast<const char*>([data bytes]),
                                       [data length]);
  if (base::Base64Decode(encoded_bytes, &decoded)) {
    DomainSuggestions suggestions;
    suggestions.ParseFromString(decoded);
    if (suggestions.has_password_requirements()) {
      self.spec = suggestions.password_requirements();
    }
  }
  [self executeCompletion];
}

// Executes the completion if present. And releases it after.
- (void)executeCompletion {
  if (self.completion) {
    auto completion = self.completion;
    self.completion = nil;
    dispatch_async(dispatch_get_main_queue(), ^{
      completion(self.spec);
    });
  }
}

@end