blob: bc70e1148dd84ee005817b10f235867edb0168c2 [file] [log] [blame]
// 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