required libs:
* ZipArchive - Obj-C impl of zip
* asi-http-request : http request help to assist with asynchoronous downloading of files
* minizip : support for ZipArchive
*
Added default splash screen for iOS app. (using the Wagic background to keep it neutral to module)
TODO: refine handling for iPad splash screen
* add selection screen and input screen for location of downloadable content. (ie core files, image files, etc )
* add support to opt out of backing up to iCloud for core files. Right now iOS will automatically backup all files under Documents folder to iCloud. Consider only allowing player data to be backed up to iCloud. All graphics and other assets are considered volatile.
488 lines
16 KiB
Objective-C
488 lines
16 KiB
Objective-C
//
|
|
// ASIAuthenticationDialog.m
|
|
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
|
|
//
|
|
// Created by Ben Copsey on 21/08/2009.
|
|
// Copyright 2009 All-Seeing Interactive. All rights reserved.
|
|
//
|
|
|
|
#import "ASIAuthenticationDialog.h"
|
|
#import "ASIHTTPRequest.h"
|
|
#import <QuartzCore/QuartzCore.h>
|
|
|
|
static ASIAuthenticationDialog *sharedDialog = nil;
|
|
BOOL isDismissing = NO;
|
|
static NSMutableArray *requestsNeedingAuthentication = nil;
|
|
|
|
static const NSUInteger kUsernameRow = 0;
|
|
static const NSUInteger kUsernameSection = 0;
|
|
static const NSUInteger kPasswordRow = 1;
|
|
static const NSUInteger kPasswordSection = 0;
|
|
static const NSUInteger kDomainRow = 0;
|
|
static const NSUInteger kDomainSection = 1;
|
|
|
|
|
|
@implementation ASIAutorotatingViewController
|
|
|
|
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@interface ASIAuthenticationDialog ()
|
|
- (void)showTitle;
|
|
- (void)show;
|
|
- (NSArray *)requestsRequiringTheseCredentials;
|
|
- (void)presentNextDialog;
|
|
- (void)keyboardWillShow:(NSNotification *)notification;
|
|
- (void)orientationChanged:(NSNotification *)notification;
|
|
- (void)cancelAuthenticationFromDialog:(id)sender;
|
|
- (void)loginWithCredentialsFromDialog:(id)sender;
|
|
@property (retain) UITableView *tableView;
|
|
@end
|
|
|
|
@implementation ASIAuthenticationDialog
|
|
|
|
#pragma mark init / dealloc
|
|
|
|
+ (void)initialize
|
|
{
|
|
if (self == [ASIAuthenticationDialog class]) {
|
|
requestsNeedingAuthentication = [[NSMutableArray array] retain];
|
|
}
|
|
}
|
|
|
|
+ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)theRequest
|
|
{
|
|
// No need for a lock here, this will always be called on the main thread
|
|
if (!sharedDialog) {
|
|
sharedDialog = [[self alloc] init];
|
|
[sharedDialog setRequest:theRequest];
|
|
if ([theRequest authenticationNeeded] == ASIProxyAuthenticationNeeded) {
|
|
[sharedDialog setType:ASIProxyAuthenticationType];
|
|
} else {
|
|
[sharedDialog setType:ASIStandardAuthenticationType];
|
|
}
|
|
[sharedDialog show];
|
|
} else {
|
|
[requestsNeedingAuthentication addObject:theRequest];
|
|
}
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
if ((self = [self initWithNibName:nil bundle:nil])) {
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
|
|
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
|
|
#endif
|
|
if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) {
|
|
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
|
|
[self setDidEnableRotationNotifications:YES];
|
|
}
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
|
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
|
}
|
|
#endif
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
if ([self didEnableRotationNotifications]) {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
|
|
}
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
|
|
|
|
[request release];
|
|
[tableView release];
|
|
[presentingController.view removeFromSuperview];
|
|
[presentingController release];
|
|
[super dealloc];
|
|
}
|
|
|
|
#pragma mark keyboard notifications
|
|
|
|
- (void)keyboardWillShow:(NSNotification *)notification
|
|
{
|
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
|
|
#endif
|
|
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2
|
|
NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey];
|
|
#else
|
|
NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey];
|
|
#endif
|
|
CGRect keyboardBounds;
|
|
[keyboardBoundsValue getValue:&keyboardBounds];
|
|
UIEdgeInsets e = UIEdgeInsetsMake(0, 0, keyboardBounds.size.height, 0);
|
|
[[self tableView] setScrollIndicatorInsets:e];
|
|
[[self tableView] setContentInset:e];
|
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Manually handles orientation changes on iPhone
|
|
- (void)orientationChanged:(NSNotification *)notification
|
|
{
|
|
[self showTitle];
|
|
|
|
UIInterfaceOrientation o = (UIInterfaceOrientation)[[UIApplication sharedApplication] statusBarOrientation];
|
|
CGFloat angle = 0;
|
|
switch (o) {
|
|
case UIDeviceOrientationLandscapeLeft: angle = 90; break;
|
|
case UIDeviceOrientationLandscapeRight: angle = -90; break;
|
|
case UIDeviceOrientationPortraitUpsideDown: angle = 180; break;
|
|
default: break;
|
|
}
|
|
|
|
CGRect f = [[UIScreen mainScreen] applicationFrame];
|
|
|
|
// Swap the frame height and width if necessary
|
|
if (UIDeviceOrientationIsLandscape(o)) {
|
|
CGFloat t;
|
|
t = f.size.width;
|
|
f.size.width = f.size.height;
|
|
f.size.height = t;
|
|
}
|
|
|
|
CGAffineTransform previousTransform = self.view.layer.affineTransform;
|
|
CGAffineTransform newTransform = CGAffineTransformMakeRotation((CGFloat)(angle * M_PI / 180.0));
|
|
|
|
// Reset the transform so we can set the size
|
|
self.view.layer.affineTransform = CGAffineTransformIdentity;
|
|
self.view.frame = (CGRect){ { 0, 0 }, f.size};
|
|
|
|
// Revert to the previous transform for correct animation
|
|
self.view.layer.affineTransform = previousTransform;
|
|
|
|
[UIView beginAnimations:nil context:NULL];
|
|
[UIView setAnimationDuration:0.3];
|
|
|
|
// Set the new transform
|
|
self.view.layer.affineTransform = newTransform;
|
|
|
|
// Fix the view origin
|
|
self.view.frame = (CGRect){ { f.origin.x, f.origin.y },self.view.frame.size};
|
|
[UIView commitAnimations];
|
|
}
|
|
|
|
#pragma mark utilities
|
|
|
|
- (UIViewController *)presentingController
|
|
{
|
|
if (!presentingController) {
|
|
presentingController = [[ASIAutorotatingViewController alloc] initWithNibName:nil bundle:nil];
|
|
|
|
// Attach to the window, but don't interfere.
|
|
UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0];
|
|
[window addSubview:[presentingController view]];
|
|
[[presentingController view] setFrame:CGRectZero];
|
|
[[presentingController view] setUserInteractionEnabled:NO];
|
|
}
|
|
|
|
return presentingController;
|
|
}
|
|
|
|
- (UITextField *)textFieldInRow:(NSUInteger)row section:(NSUInteger)section
|
|
{
|
|
return [[[[[self tableView] cellForRowAtIndexPath:
|
|
[NSIndexPath indexPathForRow:row inSection:section]]
|
|
contentView] subviews] objectAtIndex:0];
|
|
}
|
|
|
|
- (UITextField *)usernameField
|
|
{
|
|
return [self textFieldInRow:kUsernameRow section:kUsernameSection];
|
|
}
|
|
|
|
- (UITextField *)passwordField
|
|
{
|
|
return [self textFieldInRow:kPasswordRow section:kPasswordSection];
|
|
}
|
|
|
|
- (UITextField *)domainField
|
|
{
|
|
return [self textFieldInRow:kDomainRow section:kDomainSection];
|
|
}
|
|
|
|
#pragma mark show / dismiss
|
|
|
|
+ (void)dismiss
|
|
{
|
|
[[sharedDialog parentViewController] dismissModalViewControllerAnimated:YES];
|
|
}
|
|
|
|
- (void)viewDidDisappear:(BOOL)animated
|
|
{
|
|
[self retain];
|
|
[sharedDialog release];
|
|
sharedDialog = nil;
|
|
[self performSelector:@selector(presentNextDialog) withObject:nil afterDelay:0];
|
|
[self release];
|
|
}
|
|
|
|
- (void)dismiss
|
|
{
|
|
if (self == sharedDialog) {
|
|
[[self class] dismiss];
|
|
} else {
|
|
[[self parentViewController] dismissModalViewControllerAnimated:YES];
|
|
}
|
|
}
|
|
|
|
- (void)showTitle
|
|
{
|
|
UINavigationBar *navigationBar = [[[self view] subviews] objectAtIndex:0];
|
|
UINavigationItem *navItem = [[navigationBar items] objectAtIndex:0];
|
|
if (UIInterfaceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
|
|
// Setup the title
|
|
if ([self type] == ASIProxyAuthenticationType) {
|
|
[navItem setPrompt:@"Login to this secure proxy server."];
|
|
} else {
|
|
[navItem setPrompt:@"Login to this secure server."];
|
|
}
|
|
} else {
|
|
[navItem setPrompt:nil];
|
|
}
|
|
[navigationBar sizeToFit];
|
|
CGRect f = [[self view] bounds];
|
|
f.origin.y = [navigationBar frame].size.height;
|
|
f.size.height -= f.origin.y;
|
|
[[self tableView] setFrame:f];
|
|
}
|
|
|
|
- (void)show
|
|
{
|
|
// Remove all subviews
|
|
UIView *v;
|
|
while ((v = [[[self view] subviews] lastObject])) {
|
|
[v removeFromSuperview];
|
|
}
|
|
|
|
// Setup toolbar
|
|
UINavigationBar *bar = [[[UINavigationBar alloc] init] autorelease];
|
|
[bar setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
|
|
|
|
UINavigationItem *navItem = [[[UINavigationItem alloc] init] autorelease];
|
|
bar.items = [NSArray arrayWithObject:navItem];
|
|
|
|
[[self view] addSubview:bar];
|
|
|
|
[self showTitle];
|
|
|
|
// Setup toolbar buttons
|
|
if ([self type] == ASIProxyAuthenticationType) {
|
|
[navItem setTitle:[[self request] proxyHost]];
|
|
} else {
|
|
[navItem setTitle:[[[self request] url] host]];
|
|
}
|
|
|
|
[navItem setLeftBarButtonItem:[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAuthenticationFromDialog:)] autorelease]];
|
|
[navItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Login" style:UIBarButtonItemStyleDone target:self action:@selector(loginWithCredentialsFromDialog:)] autorelease]];
|
|
|
|
// We show the login form in a table view, similar to Safari's authentication dialog
|
|
[bar sizeToFit];
|
|
CGRect f = [[self view] bounds];
|
|
f.origin.y = [bar frame].size.height;
|
|
f.size.height -= f.origin.y;
|
|
|
|
[self setTableView:[[[UITableView alloc] initWithFrame:f style:UITableViewStyleGrouped] autorelease]];
|
|
[[self tableView] setDelegate:self];
|
|
[[self tableView] setDataSource:self];
|
|
[[self tableView] setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
|
|
[[self view] addSubview:[self tableView]];
|
|
|
|
// Force reload the table content, and focus the first field to show the keyboard
|
|
[[self tableView] reloadData];
|
|
[[[[[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].contentView subviews] objectAtIndex:0] becomeFirstResponder];
|
|
|
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
|
[self setModalPresentationStyle:UIModalPresentationFormSheet];
|
|
}
|
|
#endif
|
|
|
|
[[self presentingController] presentModalViewController:self animated:YES];
|
|
}
|
|
|
|
#pragma mark button callbacks
|
|
|
|
- (void)cancelAuthenticationFromDialog:(id)sender
|
|
{
|
|
for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) {
|
|
[theRequest cancelAuthentication];
|
|
[requestsNeedingAuthentication removeObject:theRequest];
|
|
}
|
|
[self dismiss];
|
|
}
|
|
|
|
- (NSArray *)requestsRequiringTheseCredentials
|
|
{
|
|
NSMutableArray *requestsRequiringTheseCredentials = [NSMutableArray array];
|
|
NSURL *requestURL = [[self request] url];
|
|
for (ASIHTTPRequest *otherRequest in requestsNeedingAuthentication) {
|
|
NSURL *theURL = [otherRequest url];
|
|
if (([otherRequest authenticationNeeded] == [[self request] authenticationNeeded]) && [[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]] && ((![otherRequest authenticationRealm] && ![[self request] authenticationRealm]) || ([otherRequest authenticationRealm] && [[self request] authenticationRealm] && [[[self request] authenticationRealm] isEqualToString:[otherRequest authenticationRealm]]))) {
|
|
[requestsRequiringTheseCredentials addObject:otherRequest];
|
|
}
|
|
}
|
|
[requestsRequiringTheseCredentials addObject:[self request]];
|
|
return requestsRequiringTheseCredentials;
|
|
}
|
|
|
|
- (void)presentNextDialog
|
|
{
|
|
if ([requestsNeedingAuthentication count]) {
|
|
ASIHTTPRequest *nextRequest = [requestsNeedingAuthentication objectAtIndex:0];
|
|
[requestsNeedingAuthentication removeObjectAtIndex:0];
|
|
[[self class] presentAuthenticationDialogForRequest:nextRequest];
|
|
}
|
|
}
|
|
|
|
|
|
- (void)loginWithCredentialsFromDialog:(id)sender
|
|
{
|
|
for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) {
|
|
|
|
NSString *username = [[self usernameField] text];
|
|
NSString *password = [[self passwordField] text];
|
|
|
|
if (username == nil) { username = @""; }
|
|
if (password == nil) { password = @""; }
|
|
|
|
if ([self type] == ASIProxyAuthenticationType) {
|
|
[theRequest setProxyUsername:username];
|
|
[theRequest setProxyPassword:password];
|
|
} else {
|
|
[theRequest setUsername:username];
|
|
[theRequest setPassword:password];
|
|
}
|
|
|
|
// Handle NTLM domains
|
|
NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
|
|
if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
|
|
NSString *domain = [[self domainField] text];
|
|
if ([self type] == ASIProxyAuthenticationType) {
|
|
[theRequest setProxyDomain:domain];
|
|
} else {
|
|
[theRequest setDomain:domain];
|
|
}
|
|
}
|
|
|
|
[theRequest retryUsingSuppliedCredentials];
|
|
[requestsNeedingAuthentication removeObject:theRequest];
|
|
}
|
|
[self dismiss];
|
|
}
|
|
|
|
#pragma mark table view data source
|
|
|
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
|
|
{
|
|
NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
|
|
if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
|
|
return 2;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
- (CGFloat)tableView:(UITableView *)aTableView heightForFooterInSection:(NSInteger)section
|
|
{
|
|
if (section == [self numberOfSectionsInTableView:aTableView]-1) {
|
|
return 30;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
- (CGFloat)tableView:(UITableView *)aTableView heightForHeaderInSection:(NSInteger)section
|
|
{
|
|
if (section == 0) {
|
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
|
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
|
|
return 54;
|
|
}
|
|
#endif
|
|
return 30;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
|
|
{
|
|
if (section == 0) {
|
|
return [[self request] authenticationRealm];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_0
|
|
UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
|
|
#else
|
|
UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,0,0) reuseIdentifier:nil] autorelease];
|
|
#endif
|
|
|
|
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
|
|
|
|
CGRect f = CGRectInset([cell bounds], 10, 10);
|
|
UITextField *textField = [[[UITextField alloc] initWithFrame:f] autorelease];
|
|
[textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
|
|
[textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
|
|
[textField setAutocorrectionType:UITextAutocorrectionTypeNo];
|
|
|
|
NSUInteger s = [indexPath section];
|
|
NSUInteger r = [indexPath row];
|
|
|
|
if (s == kUsernameSection && r == kUsernameRow) {
|
|
[textField setPlaceholder:@"User"];
|
|
} else if (s == kPasswordSection && r == kPasswordRow) {
|
|
[textField setPlaceholder:@"Password"];
|
|
[textField setSecureTextEntry:YES];
|
|
} else if (s == kDomainSection && r == kDomainRow) {
|
|
[textField setPlaceholder:@"Domain"];
|
|
}
|
|
[cell.contentView addSubview:textField];
|
|
|
|
return cell;
|
|
}
|
|
|
|
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
|
|
{
|
|
if (section == 0) {
|
|
return 2;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
- (NSString *)tableView:(UITableView *)aTableView titleForFooterInSection:(NSInteger)section
|
|
{
|
|
if (section == [self numberOfSectionsInTableView:aTableView]-1) {
|
|
// If we're using Basic authentication and the connection is not using SSL, we'll show the plain text message
|
|
if ([[[self request] authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && ![[[[self request] url] scheme] isEqualToString:@"https"]) {
|
|
return @"Password will be sent in the clear.";
|
|
// We are using Digest, NTLM, or any scheme over SSL
|
|
} else {
|
|
return @"Password will be sent securely.";
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
@synthesize request;
|
|
@synthesize type;
|
|
@synthesize tableView;
|
|
@synthesize didEnableRotationNotifications;
|
|
@synthesize presentingController;
|
|
@end
|