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 {
DOWNLOAD_PASS_KIT_SUCCESSFUL = 0,
// PassKit download failed for a reason other than wrong MIME type or 401/403
// HTTP response.
DOWNLOAD_PASS_KIT_OTHER_FAILURE,
// PassKit download failed due to either a 401 or 403 HTTP response.
DOWNLOAD_PASS_KIT_UNAUTHORIZED_FAILURE,
// PassKit download did not download the correct MIME type. This can happen
// when web server redirects to login page instead of returning PassKit data.
DOWNLOAD_PASS_KIT_WRONG_MIME_TYPE_FAILURE,
DOWNLOAD_PASS_KIT_RESULT_COUNT
};
} // 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;
@end
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 {
public:
explicit PassKitFetcherDelegate(CRWPassKitDownloader* owner)
: owner_(owner) {}
void OnURLFetchComplete(const URLFetcher* source) override {
[owner_ didFinishDownload];
}
private:
base::WeakNSObject<CRWPassKitDownloader> owner_;
DISALLOW_COPY_AND_ASSIGN(PassKitFetcherDelegate);
};
} // 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) {
DCHECK(getter);
DCHECK(handler);
_completionHandler.reset([handler copy]);
_fetcherDelegate.reset(new PassKitFetcherDelegate(self));
_requestContextGetter = getter;
_passKitDownloaderID = g_pass_kit_downloader_class_id++;
}
return self;
}
- (instancetype)init {
NOTREACHED();
return nil;
}
- (void)dealloc {
[[CRWNetworkActivityIndicatorManager sharedInstance]
clearNetworkTasksForGroup:[self networkActivityKey]];
[super dealloc];
}
- (BOOL)isMIMETypePassKitType:(NSString*)MIMEType {
return [MIMEType isEqualToString:@"application/vnd.apple.pkpass"];
}
- (void)downloadPassKitFileWithURL:(const GURL&)URL {
_fetcher = URLFetcher::Create(URL, URLFetcher::GET, _fetcherDelegate.get());
_fetcher->SetRequestContext(_requestContextGetter.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]];
_fetcher->Start();
}
- (void)cancelPendingDownload {
_fetcher.reset();
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)
? DOWNLOAD_PASS_KIT_UNAUTHORIZED_FAILURE
: DOWNLOAD_PASS_KIT_OTHER_FAILURE;
[self reportUMAPassKitResult:errorType];
_completionHandler.get()(nil);
return;
}
std::string MIMEType;
_fetcher->GetResponseHeaders()->GetMimeType(&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])
? DOWNLOAD_PASS_KIT_SUCCESSFUL
: DOWNLOAD_PASS_KIT_WRONG_MIME_TYPE_FAILURE;
[self reportUMAPassKitResult:successOrFailureLogging];
NSData* data =
[NSData dataWithBytes:response.c_str() length:response.length()];
_completionHandler.get()(data);
}
- (NSString*)networkActivityKey {
return [NSString
stringWithFormat:@"PassKitDownloader.NetworkActivityIndicatorKey.%d",
_passKitDownloaderID];
}
- (void)reportUMAPassKitResult:(DownloadPassKitResult)result {
UMA_HISTOGRAM_ENUMERATION(kUMADownloadPassKitResult, result,
DOWNLOAD_PASS_KIT_RESULT_COUNT);
}
@end