blob: 15b601241afc39915fdd6ca037d0141c237923bf [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web/web_state/crw_pass_kit_downloader.h"
#include <memory>
#import "base/ios/weak_nsobject.h"
#include "base/mac/scoped_block.h"
#include "base/macros.h"
#include "base/metrics/histogram.h"
#include "base/strings/sys_string_conversions.h"
#import "ios/web/crw_network_activity_indicator_manager.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_context_getter.h"
using net::URLFetcher;
using net::URLFetcherDelegate;
using net::URLRequestContextGetter;
namespace {
// Key of the UMA Download.IOSDownloadPassKitResult histogram.
const char kUMADownloadPassKitResult[] = "Download.IOSDownloadPassKitResult";
// Enum for the Download.IOSDownloadPassKitResult UMA histogram to report the
// results of the PassKit download.
// Note: This enum is used to back an UMA histogram, and should be treated as
// append-only.
enum DownloadPassKitResult {
// PassKit download failed for a reason other than wrong MIME type or 401/403
// HTTP response.
// PassKit download failed due to either a 401 or 403 HTTP response.
// PassKit download did not download the correct MIME type. This can happen
// when web server redirects to login page instead of returning PassKit data.
} // namespace
@interface CRWPassKitDownloader ()
// The method called by PassKitFetcherDelegate when the download is complete.
// If data is successfully downloaded, it converts the response to
// NSData and passes the result to |_completionHandler|.
- (void)didFinishDownload;
// Returns key for CRWNetworkActivityIndicatorManager.
- (NSString*)networkActivityKey;
// Reports Download.IOSDownloadPassKitResult UMA metric.
- (void)reportUMAPassKitResult:(DownloadPassKitResult)result;
namespace {
// Unique ID for CRWNetworkActivityIndicatorManager.
int g_pass_kit_downloader_class_id = 0;
// A delegate for the URLFetcher to tell the CRWPassKitDownloader that the
// download is complete.
class PassKitFetcherDelegate : public URLFetcherDelegate {
explicit PassKitFetcherDelegate(CRWPassKitDownloader* owner)
: owner_(owner) {}
void OnURLFetchComplete(const URLFetcher* source) override {
[owner_ didFinishDownload];
base::WeakNSObject<CRWPassKitDownloader> owner_;
} // namespace
@implementation CRWPassKitDownloader {
// Completion handler that is called when PassKit data is downloaded.
base::mac::ScopedBlock<web::PassKitCompletionHandler> _completionHandler;
// URLFetcher with which PassKit data is downloaded. It is initialized
// whenever |downloadPassKitFileWithURL| is called.
std::unique_ptr<URLFetcher> _fetcher;
// Delegate to bridge between URLFetcher callback and CRWPassKitDownlaoder.
std::unique_ptr<PassKitFetcherDelegate> _fetcherDelegate;
// Context getter which is passed to the URLFetcher, as required by
// URLFetcher API.
scoped_refptr<URLRequestContextGetter> _requestContextGetter;
// Network activity ID for this instance of CRWPassKitDownloader.
int _passKitDownloaderID;
#pragma mark - Public Methods
- (instancetype)initWithContextGetter:(net::URLRequestContextGetter*)getter
completionHandler:(web::PassKitCompletionHandler)handler {
self = [super init];
if (self) {
_completionHandler.reset([handler copy]);
_fetcherDelegate.reset(new PassKitFetcherDelegate(self));
_requestContextGetter = getter;
_passKitDownloaderID = g_pass_kit_downloader_class_id++;
return self;
- (instancetype)init {
return nil;
- (void)dealloc {
[[CRWNetworkActivityIndicatorManager sharedInstance]
clearNetworkTasksForGroup:[self networkActivityKey]];
[super dealloc];
- (BOOL)isMIMETypePassKitType:(NSString*)MIMEType {
return [MIMEType isEqualToString:@"application/"];
- (void)downloadPassKitFileWithURL:(const GURL&)URL {
_fetcher = URLFetcher::Create(URL, URLFetcher::GET, _fetcherDelegate.get());
CRWNetworkActivityIndicatorManager* sharedManager =
[CRWNetworkActivityIndicatorManager sharedInstance];
// Verifies that there are not any network tasks associated with this instance
// before starting another task, so that this method is idempotent.
if (![sharedManager numNetworkTasksForGroup:[self networkActivityKey]])
[sharedManager startNetworkTaskForGroup:[self networkActivityKey]];
- (void)cancelPendingDownload {
CRWNetworkActivityIndicatorManager* sharedManager =
[CRWNetworkActivityIndicatorManager sharedInstance];
// Verifies that there is a network task associated with this instance
// before stopping a task, so that this method is idempotent.
if ([sharedManager numNetworkTasksForGroup:[self networkActivityKey]])
[sharedManager stopNetworkTaskForGroup:[self networkActivityKey]];
#pragma mark - Private Methods
- (void)didFinishDownload {
[[CRWNetworkActivityIndicatorManager sharedInstance]
stopNetworkTaskForGroup:[self networkActivityKey]];
int responseCode = _fetcher->GetResponseCode();
std::string response;
// If the download failed, pass nil to |_completionHandler| and log which
// kind of failure it was.
if (!_fetcher->GetStatus().is_success() || responseCode != 200 ||
!_fetcher->GetResponseAsString(&response)) {
DownloadPassKitResult errorType =
(responseCode == 401 || responseCode == 403)
[self reportUMAPassKitResult:errorType];
std::string MIMEType;
NSString* convertedMIMEType = base::SysUTF8ToNSString(MIMEType);
// Verify for logging purposes that the data actually is PassKit data. The
// completion handler is responsible for displaying the appropriate error
// message if it isn't. This error case can occur when web server redirects to
// another page instead of returning PassKit data.
DownloadPassKitResult successOrFailureLogging =
([self isMIMETypePassKitType:convertedMIMEType])
[self reportUMAPassKitResult:successOrFailureLogging];
NSData* data =
[NSData dataWithBytes:response.c_str() length:response.length()];
- (NSString*)networkActivityKey {
return [NSString
- (void)reportUMAPassKitResult:(DownloadPassKitResult)result {
UMA_HISTOGRAM_ENUMERATION(kUMADownloadPassKitResult, result,