From 7e63f499ca45a2de6a1111e5f1529535f8bfc4c8 Mon Sep 17 00:00:00 2001 From: jrroca Date: Sun, 8 May 2011 11:10:55 +0200 Subject: [PATCH 1/6] Reaorganize source tree. --- Classes/MyKeePassAppDelegate.h | 31 +++-- Classes/MyKeePassAppDelegate.m | 106 ++++++++++++----- .../ASIHTTPRequest/ASIAuthenticationDialog.h | 0 .../ASIHTTPRequest/ASIAuthenticationDialog.m | 0 .../ASIHTTPRequest/ASIFormDataRequest.h | 0 .../ASIHTTPRequest/ASIFormDataRequest.m | 0 .../ASIHTTPRequest/ASIHTTPRequest.h | 0 .../ASIHTTPRequest/ASIHTTPRequest.m | 0 .../ASIHTTPRequest/ASIHTTPRequestConfig.h | 0 .../ASIHTTPRequest/ASIInputStream.h | 0 .../ASIHTTPRequest/ASIInputStream.m | 0 .../ASIHTTPRequest/ASINSStringAdditions.h | 0 .../ASIHTTPRequest/ASINSStringAdditions.m | 0 .../ASIHTTPRequest/ASINetworkQueue.h | 0 .../ASIHTTPRequest/ASINetworkQueue.m | 0 .../ThirdParty/ASIHTTPRequest/Reachability.h | 0 .../ThirdParty/ASIHTTPRequest/Reachability.m | 0 .../CocoaHttpServer/Categories/DDData.h | 0 .../CocoaHttpServer/Categories/DDData.m | 0 .../CocoaHttpServer/Categories/DDNumber.h | 0 .../CocoaHttpServer/Categories/DDNumber.m | 0 .../CocoaHttpServer/Categories/DDRange.h | 0 .../CocoaHttpServer/Categories/DDRange.m | 0 .../Classes/MyHTTPConnection.h | 0 .../Classes/MyHTTPConnection.m | 0 .../Classes/localhostAddresses.h | 0 .../Classes/localhostAddresses.m | 0 .../HTTP/HTTPAuthenticationRequest.h | 0 .../HTTP/HTTPAuthenticationRequest.m | 0 .../CocoaHttpServer/HTTP/HTTPConnection.h | 0 .../CocoaHttpServer/HTTP/HTTPConnection.m | 0 .../CocoaHttpServer/HTTP/HTTPResponse.h | 0 .../CocoaHttpServer/HTTP/HTTPResponse.m | 0 .../CocoaHttpServer/HTTP/HTTPServer.h | 0 .../CocoaHttpServer/HTTP/HTTPServer.m | 0 .../CocoaHttpServer/TCP/AsyncSocket.h | 0 .../CocoaHttpServer/TCP/AsyncSocket.m | 0 .../ThirdParty/Tapku/TKLabelCell.h | 0 .../ThirdParty/Tapku/TKLabelCell.m | 0 .../ThirdParty/Tapku/TKLabelTextFieldCell.h | 0 .../ThirdParty/Tapku/TKLabelTextFieldCell.m | 0 .../ThirdParty/Tapku/TKTextViewCell.h | 0 .../ThirdParty/Tapku/TKTextViewCell.m | 0 .../ThirdParty/Tapku/TextFieldCell.h | 0 .../ThirdParty/Tapku/TextFieldCell.m | 0 .../UI/AboutViewController.h | 0 .../UI/AboutViewController.m | 0 ActivityView.h => Classes/UI/ActivityView.h | 0 ActivityView.m => Classes/UI/ActivityView.m | 0 FileManager.h => Classes/UI/FileManager.h | 0 FileManager.m => Classes/UI/FileManager.m | 0 .../UI/GroupedSectionHeader.h | 0 .../UI/GroupedSectionHeader.m | 0 .../UI/Kdb/EditNodeNameViewController.h | 0 .../UI/Kdb/EditNodeNameViewController.m | 0 .../UI/Kdb/EditNodeViewController.h | 0 .../UI/Kdb/EditNodeViewController.m | 0 .../UI/Kdb/EntrySearchViewController.h | 0 .../UI/Kdb/EntrySearchViewController.m | 0 .../UI/Kdb/EntryViewController.h | 0 .../UI/Kdb/EntryViewController.m | 0 .../UI/Kdb/GroupViewController.h | 0 .../UI/Kdb/GroupViewController.m | 0 .../UI/Kdb/KdbViewController.h | 0 .../UI/Kdb/KdbViewController.m | 0 .../UI/Kdb/NewEntryViewController.h | 0 .../UI/Kdb/NewEntryViewController.m | 0 .../UI/Kdb/NewGroupViewController.h | 0 .../UI/Kdb/NewGroupViewController.m | 0 .../UI/Kdb/OptionViewController.h | 0 .../UI/Kdb/OptionViewController.m | 0 .../UI/Kdb/PasswordDisclosureView.h | 0 .../UI/Kdb/PasswordDisclosureView.m | 0 Sort.h => Classes/UI/Kdb/Sort.h | 0 Sort.m => Classes/UI/Kdb/Sort.m | 0 .../UI/Main/FileManagerOperation.h | 0 .../UI/Main/FileManagerOperation.m | 0 .../UI/Main/FileUploadViewController.h | 0 .../UI/Main/FileUploadViewController.m | 0 .../UI/Main/FileViewController.h | 0 .../UI/Main/FileViewController.m | 0 .../UI/Main/MainViewController.h | 0 .../UI/Main/MainViewController.m | 0 .../UI/Main/NewLocalFileViewController.h | 0 .../UI/Main/NewLocalFileViewController.m | 0 .../UI/Main/NewRemoteFileViewController.h | 0 .../UI/Main/NewRemoteFileViewController.m | 0 .../UI/Main/PasswordViewController.h | 0 .../UI/Main/PasswordViewController.m | 0 .../UI/Main/RenameFileViewController.h | 0 .../UI/Main/RenameFileViewController.m | 0 .../UI/Main/RenameRemoteFileViewController.h | 0 .../UI/Main/RenameRemoteFileViewController.m | 0 .../UI/Main/SettingViewController.h | 0 .../UI/Main/SettingViewController.m | 0 .../UI/MultiLineTableViewCellStyle2.h | 0 .../UI/MultiLineTableViewCellStyle2.m | 0 .../UI/PlainSectionHeader.h | 0 .../UI/PlainSectionHeader.m | 0 MyKeePass.xcodeproj/project.pbxproj | 25 ++-- MyKeePassAppDelegate.h | 39 ------- MyKeePassAppDelegate.m | 108 ------------------ Default.png => images/Default.png | Bin Icon-Small.png => images/Icon-Small.png | Bin Icon.png => images/Icon.png | Bin about.png => images/about.png | Bin add.png => images/add.png | Bin addPressed.png => images/addPressed.png | Bin files.png => images/files.png | Bin http.png => images/http.png | Bin kdb.png => images/kdb.png | Bin kdbx.png => images/kdbx.png | Bin options.png => images/options.png | Bin password.png => images/password.png | Bin .../passwordPressed.png | Bin passwords.png => images/passwords.png | Bin unknown.png => images/unknown.png | Bin 117 files changed, 113 insertions(+), 196 deletions(-) rename ASIAuthenticationDialog.h => Classes/ThirdParty/ASIHTTPRequest/ASIAuthenticationDialog.h (100%) rename ASIAuthenticationDialog.m => Classes/ThirdParty/ASIHTTPRequest/ASIAuthenticationDialog.m (100%) rename ASIFormDataRequest.h => Classes/ThirdParty/ASIHTTPRequest/ASIFormDataRequest.h (100%) rename ASIFormDataRequest.m => Classes/ThirdParty/ASIHTTPRequest/ASIFormDataRequest.m (100%) rename ASIHTTPRequest.h => Classes/ThirdParty/ASIHTTPRequest/ASIHTTPRequest.h (100%) rename ASIHTTPRequest.m => Classes/ThirdParty/ASIHTTPRequest/ASIHTTPRequest.m (100%) rename ASIHTTPRequestConfig.h => Classes/ThirdParty/ASIHTTPRequest/ASIHTTPRequestConfig.h (100%) rename ASIInputStream.h => Classes/ThirdParty/ASIHTTPRequest/ASIInputStream.h (100%) rename ASIInputStream.m => Classes/ThirdParty/ASIHTTPRequest/ASIInputStream.m (100%) rename ASINSStringAdditions.h => Classes/ThirdParty/ASIHTTPRequest/ASINSStringAdditions.h (100%) rename ASINSStringAdditions.m => Classes/ThirdParty/ASIHTTPRequest/ASINSStringAdditions.m (100%) rename ASINetworkQueue.h => Classes/ThirdParty/ASIHTTPRequest/ASINetworkQueue.h (100%) rename ASINetworkQueue.m => Classes/ThirdParty/ASIHTTPRequest/ASINetworkQueue.m (100%) rename Reachability.h => Classes/ThirdParty/ASIHTTPRequest/Reachability.h (100%) rename Reachability.m => Classes/ThirdParty/ASIHTTPRequest/Reachability.m (100%) rename DDData.h => Classes/ThirdParty/CocoaHttpServer/Categories/DDData.h (100%) rename DDData.m => Classes/ThirdParty/CocoaHttpServer/Categories/DDData.m (100%) rename DDNumber.h => Classes/ThirdParty/CocoaHttpServer/Categories/DDNumber.h (100%) rename DDNumber.m => Classes/ThirdParty/CocoaHttpServer/Categories/DDNumber.m (100%) rename DDRange.h => Classes/ThirdParty/CocoaHttpServer/Categories/DDRange.h (100%) rename DDRange.m => Classes/ThirdParty/CocoaHttpServer/Categories/DDRange.m (100%) rename MyHTTPConnection.h => Classes/ThirdParty/CocoaHttpServer/Classes/MyHTTPConnection.h (100%) rename MyHTTPConnection.m => Classes/ThirdParty/CocoaHttpServer/Classes/MyHTTPConnection.m (100%) rename localhostAddresses.h => Classes/ThirdParty/CocoaHttpServer/Classes/localhostAddresses.h (100%) rename localhostAddresses.m => Classes/ThirdParty/CocoaHttpServer/Classes/localhostAddresses.m (100%) rename HTTPAuthenticationRequest.h => Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPAuthenticationRequest.h (100%) rename HTTPAuthenticationRequest.m => Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPAuthenticationRequest.m (100%) rename HTTPConnection.h => Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPConnection.h (100%) rename HTTPConnection.m => Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPConnection.m (100%) rename HTTPResponse.h => Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPResponse.h (100%) rename HTTPResponse.m => Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPResponse.m (100%) rename HTTPServer.h => Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPServer.h (100%) rename HTTPServer.m => Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPServer.m (100%) rename AsyncSocket.h => Classes/ThirdParty/CocoaHttpServer/TCP/AsyncSocket.h (100%) rename AsyncSocket.m => Classes/ThirdParty/CocoaHttpServer/TCP/AsyncSocket.m (100%) rename TKLabelCell.h => Classes/ThirdParty/Tapku/TKLabelCell.h (100%) rename TKLabelCell.m => Classes/ThirdParty/Tapku/TKLabelCell.m (100%) rename TKLabelTextFieldCell.h => Classes/ThirdParty/Tapku/TKLabelTextFieldCell.h (100%) rename TKLabelTextFieldCell.m => Classes/ThirdParty/Tapku/TKLabelTextFieldCell.m (100%) rename TKTextViewCell.h => Classes/ThirdParty/Tapku/TKTextViewCell.h (100%) rename TKTextViewCell.m => Classes/ThirdParty/Tapku/TKTextViewCell.m (100%) rename TextFieldCell.h => Classes/ThirdParty/Tapku/TextFieldCell.h (100%) rename TextFieldCell.m => Classes/ThirdParty/Tapku/TextFieldCell.m (100%) rename AboutViewController.h => Classes/UI/AboutViewController.h (100%) rename AboutViewController.m => Classes/UI/AboutViewController.m (100%) rename ActivityView.h => Classes/UI/ActivityView.h (100%) rename ActivityView.m => Classes/UI/ActivityView.m (100%) rename FileManager.h => Classes/UI/FileManager.h (100%) rename FileManager.m => Classes/UI/FileManager.m (100%) rename GroupedSectionHeader.h => Classes/UI/GroupedSectionHeader.h (100%) rename GroupedSectionHeader.m => Classes/UI/GroupedSectionHeader.m (100%) rename EditNodeNameViewController.h => Classes/UI/Kdb/EditNodeNameViewController.h (100%) rename EditNodeNameViewController.m => Classes/UI/Kdb/EditNodeNameViewController.m (100%) rename EditNodeViewController.h => Classes/UI/Kdb/EditNodeViewController.h (100%) rename EditNodeViewController.m => Classes/UI/Kdb/EditNodeViewController.m (100%) rename EntrySearchViewController.h => Classes/UI/Kdb/EntrySearchViewController.h (100%) rename EntrySearchViewController.m => Classes/UI/Kdb/EntrySearchViewController.m (100%) rename EntryViewController.h => Classes/UI/Kdb/EntryViewController.h (100%) rename EntryViewController.m => Classes/UI/Kdb/EntryViewController.m (100%) rename GroupViewController.h => Classes/UI/Kdb/GroupViewController.h (100%) rename GroupViewController.m => Classes/UI/Kdb/GroupViewController.m (100%) rename KdbViewController.h => Classes/UI/Kdb/KdbViewController.h (100%) rename KdbViewController.m => Classes/UI/Kdb/KdbViewController.m (100%) rename NewEntryViewController.h => Classes/UI/Kdb/NewEntryViewController.h (100%) rename NewEntryViewController.m => Classes/UI/Kdb/NewEntryViewController.m (100%) rename NewGroupViewController.h => Classes/UI/Kdb/NewGroupViewController.h (100%) rename NewGroupViewController.m => Classes/UI/Kdb/NewGroupViewController.m (100%) rename OptionViewController.h => Classes/UI/Kdb/OptionViewController.h (100%) rename OptionViewController.m => Classes/UI/Kdb/OptionViewController.m (100%) rename PasswordDisclosureView.h => Classes/UI/Kdb/PasswordDisclosureView.h (100%) rename PasswordDisclosureView.m => Classes/UI/Kdb/PasswordDisclosureView.m (100%) rename Sort.h => Classes/UI/Kdb/Sort.h (100%) rename Sort.m => Classes/UI/Kdb/Sort.m (100%) rename FileManagerOperation.h => Classes/UI/Main/FileManagerOperation.h (100%) rename FileManagerOperation.m => Classes/UI/Main/FileManagerOperation.m (100%) rename FileUploadViewController.h => Classes/UI/Main/FileUploadViewController.h (100%) rename FileUploadViewController.m => Classes/UI/Main/FileUploadViewController.m (100%) rename FileViewController.h => Classes/UI/Main/FileViewController.h (100%) rename FileViewController.m => Classes/UI/Main/FileViewController.m (100%) rename MainViewController.h => Classes/UI/Main/MainViewController.h (100%) rename MainViewController.m => Classes/UI/Main/MainViewController.m (100%) rename NewLocalFileViewController.h => Classes/UI/Main/NewLocalFileViewController.h (100%) rename NewLocalFileViewController.m => Classes/UI/Main/NewLocalFileViewController.m (100%) rename NewRemoteFileViewController.h => Classes/UI/Main/NewRemoteFileViewController.h (100%) rename NewRemoteFileViewController.m => Classes/UI/Main/NewRemoteFileViewController.m (100%) rename PasswordViewController.h => Classes/UI/Main/PasswordViewController.h (100%) rename PasswordViewController.m => Classes/UI/Main/PasswordViewController.m (100%) rename RenameFileViewController.h => Classes/UI/Main/RenameFileViewController.h (100%) rename RenameFileViewController.m => Classes/UI/Main/RenameFileViewController.m (100%) rename RenameRemoteFileViewController.h => Classes/UI/Main/RenameRemoteFileViewController.h (100%) rename RenameRemoteFileViewController.m => Classes/UI/Main/RenameRemoteFileViewController.m (100%) rename SettingViewController.h => Classes/UI/Main/SettingViewController.h (100%) rename SettingViewController.m => Classes/UI/Main/SettingViewController.m (100%) rename MultiLineTableViewCellStyle2.h => Classes/UI/MultiLineTableViewCellStyle2.h (100%) rename MultiLineTableViewCellStyle2.m => Classes/UI/MultiLineTableViewCellStyle2.m (100%) rename PlainSectionHeader.h => Classes/UI/PlainSectionHeader.h (100%) rename PlainSectionHeader.m => Classes/UI/PlainSectionHeader.m (100%) delete mode 100644 MyKeePassAppDelegate.h delete mode 100644 MyKeePassAppDelegate.m rename Default.png => images/Default.png (100%) rename Icon-Small.png => images/Icon-Small.png (100%) rename Icon.png => images/Icon.png (100%) rename about.png => images/about.png (100%) rename add.png => images/add.png (100%) rename addPressed.png => images/addPressed.png (100%) rename files.png => images/files.png (100%) rename http.png => images/http.png (100%) rename kdb.png => images/kdb.png (100%) rename kdbx.png => images/kdbx.png (100%) rename options.png => images/options.png (100%) rename password.png => images/password.png (100%) rename passwordPressed.png => images/passwordPressed.png (100%) rename passwords.png => images/passwords.png (100%) rename unknown.png => images/unknown.png (100%) diff --git a/Classes/MyKeePassAppDelegate.h b/Classes/MyKeePassAppDelegate.h index bcd04d1..5c20d6d 100644 --- a/Classes/MyKeePassAppDelegate.h +++ b/Classes/MyKeePassAppDelegate.h @@ -2,25 +2,38 @@ // MyKeePassAppDelegate.h // MyKeePass // -// Created by Qiang Yu on 12/4/09. -// Copyright __MyCompanyName__ 2009. All rights reserved. +// Created by Qiang Yu on 3/3/10. +// Copyright Qiang Yu 2010. All rights reserved. // #import #import "MainViewController.h" -#import "DatabaseWrapper.h" +#import "KdbViewController.h" +#import "PasswordViewController.h" +#import "FileManager.h" @interface MyKeePassAppDelegate : NSObject { - UIWindow *window; - MainViewController * _mainViewController; - DatabaseWrapper * _databaseWrapper; + FileManager * _fileManager; + + MainViewController * _mainView; + KdbViewController * _kdbView; + + UIViewController * _currentViewController; + + UIWindow *window; } @property (nonatomic, retain) IBOutlet UIWindow *window; -@property (nonatomic, retain) IBOutlet MainViewController * _mainViewController; -@property(nonatomic, retain) DatabaseWrapper * _databaseWrapper; +@property (nonatomic, readonly) FileManager * _fileManager; +@property (nonatomic, retain) MainViewController * _mainView; +@property (nonatomic, retain) KdbViewController * _kdbView; +@property (nonatomic, retain) UIViewController * _currentViewController; --(void)switchViews; ++(MyKeePassAppDelegate *)delegate; ++(UIWindow *)getWindow; +-(void)showKdb; +-(void)showMainView; +-(BOOL)isEditable; @end diff --git a/Classes/MyKeePassAppDelegate.m b/Classes/MyKeePassAppDelegate.m index e5205b8..aa70fde 100644 --- a/Classes/MyKeePassAppDelegate.m +++ b/Classes/MyKeePassAppDelegate.m @@ -2,63 +2,107 @@ // MyKeePassAppDelegate.m // MyKeePass // -// Created by Qiang Yu on 12/4/09. -// Copyright __MyCompanyName__ 2009. All rights reserved. +// Created by Qiang Yu on 3/3/10. +// Copyright Qiang Yu 2010. All rights reserved. // +#import #import "MyKeePassAppDelegate.h" @implementation MyKeePassAppDelegate - @synthesize window; -@synthesize _mainViewController; -@synthesize _databaseWrapper; - --(id)init{ - if(self==[super init]){ - self._databaseWrapper = [[DatabaseWrapper alloc]init]; - } - return self; -} +@synthesize _fileManager; +@synthesize _kdbView; +@synthesize _mainView; +@synthesize _currentViewController; - (void)applicationDidFinishLaunching:(UIApplication *)application { - [window addSubview:_mainViewController.view]; - // Override point for customization after application launch + //initialize the file manager + _fileManager = [[FileManager alloc]init]; + + //initialize the main view + _mainView = [[MainViewController alloc]init]; + + self._currentViewController = _mainView; + + [window addSubview:_mainView.view]; [window makeKeyAndVisible]; } - (void)dealloc { + [_mainView release]; + [_kdbView release]; [window release]; - [_mainViewController release]; - [_databaseWrapper release]; + [_fileManager release]; + [_currentViewController release]; [super dealloc]; } - --(void)switchViews{ - [_mainViewController switchViews]; +-(void)showKdb{ + if(![_currentViewController isKindOfClass:[KdbViewController class]]){ + [_currentViewController.view removeFromSuperview]; + id kdbReader = _fileManager._kdbReader; + + if(!_kdbView){ + _kdbView = [[KdbViewController alloc]initWithGroup:[[kdbReader getKdbTree] getRoot]]; + } + + self._currentViewController = _kdbView; + + [window addSubview:_currentViewController.view]; + + CATransition *animation = [CATransition animation]; + [animation setDuration:0.5]; + [animation setType:kCATransitionFade]; + [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; + [[window layer] addAnimation:animation forKey:@"SwitchToKdbView"]; + } } -- (void)applicationWillResignActive:(UIApplication *)application{ - //clean views e.g. dismiss modal views such as TableTextFieldEditViewController - [_mainViewController cleanViews]; - [_databaseWrapper saveDatabase]; - _databaseWrapper._database = nil; +-(void)showMainView{ + if(![_currentViewController isKindOfClass:[MainViewController class]]){ + [_currentViewController.view removeFromSuperview]; + _fileManager._kdbReader = nil; + self._kdbView = nil; + + if(!_mainView) + _mainView = [[MainViewController alloc]init]; + + self._currentViewController = _mainView; + + [window addSubview:_currentViewController.view]; + + CATransition *animation = [CATransition animation]; + [animation setDuration:0.5]; + [animation setType:kCATransitionFade]; + [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; + [[window layer] addAnimation:animation forKey:@"SwitchToMainView"]; + } } - (void)applicationDidBecomeActive:(UIApplication *)application{ - /// - //switch the view to fileview whenever the application becomes active - /// - if(!_mainViewController._fileViewController.view.superview){ - [self switchViews]; + if([_currentViewController isKindOfClass:[KdbViewController class]]){ + [_currentViewController.view removeFromSuperview]; + + PasswordViewController * lockWindow = [[PasswordViewController alloc] initWithFileName:_fileManager._filename remote:NO]; + lockWindow._isReopen = YES; + + self._currentViewController = lockWindow; + [window addSubview:lockWindow.view]; + [lockWindow release]; } } -- (void)applicationWillTerminate:(UIApplication *)application{ - [_databaseWrapper saveDatabase]; +-(BOOL)isEditable{ + return _fileManager._editable; } ++(MyKeePassAppDelegate *)delegate{ + return (MyKeePassAppDelegate *)[[UIApplication sharedApplication] delegate]; +} ++(UIWindow *)getWindow{ + return ((MyKeePassAppDelegate *)[[UIApplication sharedApplication] delegate]).window; +} @end diff --git a/ASIAuthenticationDialog.h b/Classes/ThirdParty/ASIHTTPRequest/ASIAuthenticationDialog.h similarity index 100% rename from ASIAuthenticationDialog.h rename to Classes/ThirdParty/ASIHTTPRequest/ASIAuthenticationDialog.h diff --git a/ASIAuthenticationDialog.m b/Classes/ThirdParty/ASIHTTPRequest/ASIAuthenticationDialog.m similarity index 100% rename from ASIAuthenticationDialog.m rename to Classes/ThirdParty/ASIHTTPRequest/ASIAuthenticationDialog.m diff --git a/ASIFormDataRequest.h b/Classes/ThirdParty/ASIHTTPRequest/ASIFormDataRequest.h similarity index 100% rename from ASIFormDataRequest.h rename to Classes/ThirdParty/ASIHTTPRequest/ASIFormDataRequest.h diff --git a/ASIFormDataRequest.m b/Classes/ThirdParty/ASIHTTPRequest/ASIFormDataRequest.m similarity index 100% rename from ASIFormDataRequest.m rename to Classes/ThirdParty/ASIHTTPRequest/ASIFormDataRequest.m diff --git a/ASIHTTPRequest.h b/Classes/ThirdParty/ASIHTTPRequest/ASIHTTPRequest.h similarity index 100% rename from ASIHTTPRequest.h rename to Classes/ThirdParty/ASIHTTPRequest/ASIHTTPRequest.h diff --git a/ASIHTTPRequest.m b/Classes/ThirdParty/ASIHTTPRequest/ASIHTTPRequest.m similarity index 100% rename from ASIHTTPRequest.m rename to Classes/ThirdParty/ASIHTTPRequest/ASIHTTPRequest.m diff --git a/ASIHTTPRequestConfig.h b/Classes/ThirdParty/ASIHTTPRequest/ASIHTTPRequestConfig.h similarity index 100% rename from ASIHTTPRequestConfig.h rename to Classes/ThirdParty/ASIHTTPRequest/ASIHTTPRequestConfig.h diff --git a/ASIInputStream.h b/Classes/ThirdParty/ASIHTTPRequest/ASIInputStream.h similarity index 100% rename from ASIInputStream.h rename to Classes/ThirdParty/ASIHTTPRequest/ASIInputStream.h diff --git a/ASIInputStream.m b/Classes/ThirdParty/ASIHTTPRequest/ASIInputStream.m similarity index 100% rename from ASIInputStream.m rename to Classes/ThirdParty/ASIHTTPRequest/ASIInputStream.m diff --git a/ASINSStringAdditions.h b/Classes/ThirdParty/ASIHTTPRequest/ASINSStringAdditions.h similarity index 100% rename from ASINSStringAdditions.h rename to Classes/ThirdParty/ASIHTTPRequest/ASINSStringAdditions.h diff --git a/ASINSStringAdditions.m b/Classes/ThirdParty/ASIHTTPRequest/ASINSStringAdditions.m similarity index 100% rename from ASINSStringAdditions.m rename to Classes/ThirdParty/ASIHTTPRequest/ASINSStringAdditions.m diff --git a/ASINetworkQueue.h b/Classes/ThirdParty/ASIHTTPRequest/ASINetworkQueue.h similarity index 100% rename from ASINetworkQueue.h rename to Classes/ThirdParty/ASIHTTPRequest/ASINetworkQueue.h diff --git a/ASINetworkQueue.m b/Classes/ThirdParty/ASIHTTPRequest/ASINetworkQueue.m similarity index 100% rename from ASINetworkQueue.m rename to Classes/ThirdParty/ASIHTTPRequest/ASINetworkQueue.m diff --git a/Reachability.h b/Classes/ThirdParty/ASIHTTPRequest/Reachability.h similarity index 100% rename from Reachability.h rename to Classes/ThirdParty/ASIHTTPRequest/Reachability.h diff --git a/Reachability.m b/Classes/ThirdParty/ASIHTTPRequest/Reachability.m similarity index 100% rename from Reachability.m rename to Classes/ThirdParty/ASIHTTPRequest/Reachability.m diff --git a/DDData.h b/Classes/ThirdParty/CocoaHttpServer/Categories/DDData.h similarity index 100% rename from DDData.h rename to Classes/ThirdParty/CocoaHttpServer/Categories/DDData.h diff --git a/DDData.m b/Classes/ThirdParty/CocoaHttpServer/Categories/DDData.m similarity index 100% rename from DDData.m rename to Classes/ThirdParty/CocoaHttpServer/Categories/DDData.m diff --git a/DDNumber.h b/Classes/ThirdParty/CocoaHttpServer/Categories/DDNumber.h similarity index 100% rename from DDNumber.h rename to Classes/ThirdParty/CocoaHttpServer/Categories/DDNumber.h diff --git a/DDNumber.m b/Classes/ThirdParty/CocoaHttpServer/Categories/DDNumber.m similarity index 100% rename from DDNumber.m rename to Classes/ThirdParty/CocoaHttpServer/Categories/DDNumber.m diff --git a/DDRange.h b/Classes/ThirdParty/CocoaHttpServer/Categories/DDRange.h similarity index 100% rename from DDRange.h rename to Classes/ThirdParty/CocoaHttpServer/Categories/DDRange.h diff --git a/DDRange.m b/Classes/ThirdParty/CocoaHttpServer/Categories/DDRange.m similarity index 100% rename from DDRange.m rename to Classes/ThirdParty/CocoaHttpServer/Categories/DDRange.m diff --git a/MyHTTPConnection.h b/Classes/ThirdParty/CocoaHttpServer/Classes/MyHTTPConnection.h similarity index 100% rename from MyHTTPConnection.h rename to Classes/ThirdParty/CocoaHttpServer/Classes/MyHTTPConnection.h diff --git a/MyHTTPConnection.m b/Classes/ThirdParty/CocoaHttpServer/Classes/MyHTTPConnection.m similarity index 100% rename from MyHTTPConnection.m rename to Classes/ThirdParty/CocoaHttpServer/Classes/MyHTTPConnection.m diff --git a/localhostAddresses.h b/Classes/ThirdParty/CocoaHttpServer/Classes/localhostAddresses.h similarity index 100% rename from localhostAddresses.h rename to Classes/ThirdParty/CocoaHttpServer/Classes/localhostAddresses.h diff --git a/localhostAddresses.m b/Classes/ThirdParty/CocoaHttpServer/Classes/localhostAddresses.m similarity index 100% rename from localhostAddresses.m rename to Classes/ThirdParty/CocoaHttpServer/Classes/localhostAddresses.m diff --git a/HTTPAuthenticationRequest.h b/Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPAuthenticationRequest.h similarity index 100% rename from HTTPAuthenticationRequest.h rename to Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPAuthenticationRequest.h diff --git a/HTTPAuthenticationRequest.m b/Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPAuthenticationRequest.m similarity index 100% rename from HTTPAuthenticationRequest.m rename to Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPAuthenticationRequest.m diff --git a/HTTPConnection.h b/Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPConnection.h similarity index 100% rename from HTTPConnection.h rename to Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPConnection.h diff --git a/HTTPConnection.m b/Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPConnection.m similarity index 100% rename from HTTPConnection.m rename to Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPConnection.m diff --git a/HTTPResponse.h b/Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPResponse.h similarity index 100% rename from HTTPResponse.h rename to Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPResponse.h diff --git a/HTTPResponse.m b/Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPResponse.m similarity index 100% rename from HTTPResponse.m rename to Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPResponse.m diff --git a/HTTPServer.h b/Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPServer.h similarity index 100% rename from HTTPServer.h rename to Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPServer.h diff --git a/HTTPServer.m b/Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPServer.m similarity index 100% rename from HTTPServer.m rename to Classes/ThirdParty/CocoaHttpServer/HTTP/HTTPServer.m diff --git a/AsyncSocket.h b/Classes/ThirdParty/CocoaHttpServer/TCP/AsyncSocket.h similarity index 100% rename from AsyncSocket.h rename to Classes/ThirdParty/CocoaHttpServer/TCP/AsyncSocket.h diff --git a/AsyncSocket.m b/Classes/ThirdParty/CocoaHttpServer/TCP/AsyncSocket.m similarity index 100% rename from AsyncSocket.m rename to Classes/ThirdParty/CocoaHttpServer/TCP/AsyncSocket.m diff --git a/TKLabelCell.h b/Classes/ThirdParty/Tapku/TKLabelCell.h similarity index 100% rename from TKLabelCell.h rename to Classes/ThirdParty/Tapku/TKLabelCell.h diff --git a/TKLabelCell.m b/Classes/ThirdParty/Tapku/TKLabelCell.m similarity index 100% rename from TKLabelCell.m rename to Classes/ThirdParty/Tapku/TKLabelCell.m diff --git a/TKLabelTextFieldCell.h b/Classes/ThirdParty/Tapku/TKLabelTextFieldCell.h similarity index 100% rename from TKLabelTextFieldCell.h rename to Classes/ThirdParty/Tapku/TKLabelTextFieldCell.h diff --git a/TKLabelTextFieldCell.m b/Classes/ThirdParty/Tapku/TKLabelTextFieldCell.m similarity index 100% rename from TKLabelTextFieldCell.m rename to Classes/ThirdParty/Tapku/TKLabelTextFieldCell.m diff --git a/TKTextViewCell.h b/Classes/ThirdParty/Tapku/TKTextViewCell.h similarity index 100% rename from TKTextViewCell.h rename to Classes/ThirdParty/Tapku/TKTextViewCell.h diff --git a/TKTextViewCell.m b/Classes/ThirdParty/Tapku/TKTextViewCell.m similarity index 100% rename from TKTextViewCell.m rename to Classes/ThirdParty/Tapku/TKTextViewCell.m diff --git a/TextFieldCell.h b/Classes/ThirdParty/Tapku/TextFieldCell.h similarity index 100% rename from TextFieldCell.h rename to Classes/ThirdParty/Tapku/TextFieldCell.h diff --git a/TextFieldCell.m b/Classes/ThirdParty/Tapku/TextFieldCell.m similarity index 100% rename from TextFieldCell.m rename to Classes/ThirdParty/Tapku/TextFieldCell.m diff --git a/AboutViewController.h b/Classes/UI/AboutViewController.h similarity index 100% rename from AboutViewController.h rename to Classes/UI/AboutViewController.h diff --git a/AboutViewController.m b/Classes/UI/AboutViewController.m similarity index 100% rename from AboutViewController.m rename to Classes/UI/AboutViewController.m diff --git a/ActivityView.h b/Classes/UI/ActivityView.h similarity index 100% rename from ActivityView.h rename to Classes/UI/ActivityView.h diff --git a/ActivityView.m b/Classes/UI/ActivityView.m similarity index 100% rename from ActivityView.m rename to Classes/UI/ActivityView.m diff --git a/FileManager.h b/Classes/UI/FileManager.h similarity index 100% rename from FileManager.h rename to Classes/UI/FileManager.h diff --git a/FileManager.m b/Classes/UI/FileManager.m similarity index 100% rename from FileManager.m rename to Classes/UI/FileManager.m diff --git a/GroupedSectionHeader.h b/Classes/UI/GroupedSectionHeader.h similarity index 100% rename from GroupedSectionHeader.h rename to Classes/UI/GroupedSectionHeader.h diff --git a/GroupedSectionHeader.m b/Classes/UI/GroupedSectionHeader.m similarity index 100% rename from GroupedSectionHeader.m rename to Classes/UI/GroupedSectionHeader.m diff --git a/EditNodeNameViewController.h b/Classes/UI/Kdb/EditNodeNameViewController.h similarity index 100% rename from EditNodeNameViewController.h rename to Classes/UI/Kdb/EditNodeNameViewController.h diff --git a/EditNodeNameViewController.m b/Classes/UI/Kdb/EditNodeNameViewController.m similarity index 100% rename from EditNodeNameViewController.m rename to Classes/UI/Kdb/EditNodeNameViewController.m diff --git a/EditNodeViewController.h b/Classes/UI/Kdb/EditNodeViewController.h similarity index 100% rename from EditNodeViewController.h rename to Classes/UI/Kdb/EditNodeViewController.h diff --git a/EditNodeViewController.m b/Classes/UI/Kdb/EditNodeViewController.m similarity index 100% rename from EditNodeViewController.m rename to Classes/UI/Kdb/EditNodeViewController.m diff --git a/EntrySearchViewController.h b/Classes/UI/Kdb/EntrySearchViewController.h similarity index 100% rename from EntrySearchViewController.h rename to Classes/UI/Kdb/EntrySearchViewController.h diff --git a/EntrySearchViewController.m b/Classes/UI/Kdb/EntrySearchViewController.m similarity index 100% rename from EntrySearchViewController.m rename to Classes/UI/Kdb/EntrySearchViewController.m diff --git a/EntryViewController.h b/Classes/UI/Kdb/EntryViewController.h similarity index 100% rename from EntryViewController.h rename to Classes/UI/Kdb/EntryViewController.h diff --git a/EntryViewController.m b/Classes/UI/Kdb/EntryViewController.m similarity index 100% rename from EntryViewController.m rename to Classes/UI/Kdb/EntryViewController.m diff --git a/GroupViewController.h b/Classes/UI/Kdb/GroupViewController.h similarity index 100% rename from GroupViewController.h rename to Classes/UI/Kdb/GroupViewController.h diff --git a/GroupViewController.m b/Classes/UI/Kdb/GroupViewController.m similarity index 100% rename from GroupViewController.m rename to Classes/UI/Kdb/GroupViewController.m diff --git a/KdbViewController.h b/Classes/UI/Kdb/KdbViewController.h similarity index 100% rename from KdbViewController.h rename to Classes/UI/Kdb/KdbViewController.h diff --git a/KdbViewController.m b/Classes/UI/Kdb/KdbViewController.m similarity index 100% rename from KdbViewController.m rename to Classes/UI/Kdb/KdbViewController.m diff --git a/NewEntryViewController.h b/Classes/UI/Kdb/NewEntryViewController.h similarity index 100% rename from NewEntryViewController.h rename to Classes/UI/Kdb/NewEntryViewController.h diff --git a/NewEntryViewController.m b/Classes/UI/Kdb/NewEntryViewController.m similarity index 100% rename from NewEntryViewController.m rename to Classes/UI/Kdb/NewEntryViewController.m diff --git a/NewGroupViewController.h b/Classes/UI/Kdb/NewGroupViewController.h similarity index 100% rename from NewGroupViewController.h rename to Classes/UI/Kdb/NewGroupViewController.h diff --git a/NewGroupViewController.m b/Classes/UI/Kdb/NewGroupViewController.m similarity index 100% rename from NewGroupViewController.m rename to Classes/UI/Kdb/NewGroupViewController.m diff --git a/OptionViewController.h b/Classes/UI/Kdb/OptionViewController.h similarity index 100% rename from OptionViewController.h rename to Classes/UI/Kdb/OptionViewController.h diff --git a/OptionViewController.m b/Classes/UI/Kdb/OptionViewController.m similarity index 100% rename from OptionViewController.m rename to Classes/UI/Kdb/OptionViewController.m diff --git a/PasswordDisclosureView.h b/Classes/UI/Kdb/PasswordDisclosureView.h similarity index 100% rename from PasswordDisclosureView.h rename to Classes/UI/Kdb/PasswordDisclosureView.h diff --git a/PasswordDisclosureView.m b/Classes/UI/Kdb/PasswordDisclosureView.m similarity index 100% rename from PasswordDisclosureView.m rename to Classes/UI/Kdb/PasswordDisclosureView.m diff --git a/Sort.h b/Classes/UI/Kdb/Sort.h similarity index 100% rename from Sort.h rename to Classes/UI/Kdb/Sort.h diff --git a/Sort.m b/Classes/UI/Kdb/Sort.m similarity index 100% rename from Sort.m rename to Classes/UI/Kdb/Sort.m diff --git a/FileManagerOperation.h b/Classes/UI/Main/FileManagerOperation.h similarity index 100% rename from FileManagerOperation.h rename to Classes/UI/Main/FileManagerOperation.h diff --git a/FileManagerOperation.m b/Classes/UI/Main/FileManagerOperation.m similarity index 100% rename from FileManagerOperation.m rename to Classes/UI/Main/FileManagerOperation.m diff --git a/FileUploadViewController.h b/Classes/UI/Main/FileUploadViewController.h similarity index 100% rename from FileUploadViewController.h rename to Classes/UI/Main/FileUploadViewController.h diff --git a/FileUploadViewController.m b/Classes/UI/Main/FileUploadViewController.m similarity index 100% rename from FileUploadViewController.m rename to Classes/UI/Main/FileUploadViewController.m diff --git a/FileViewController.h b/Classes/UI/Main/FileViewController.h similarity index 100% rename from FileViewController.h rename to Classes/UI/Main/FileViewController.h diff --git a/FileViewController.m b/Classes/UI/Main/FileViewController.m similarity index 100% rename from FileViewController.m rename to Classes/UI/Main/FileViewController.m diff --git a/MainViewController.h b/Classes/UI/Main/MainViewController.h similarity index 100% rename from MainViewController.h rename to Classes/UI/Main/MainViewController.h diff --git a/MainViewController.m b/Classes/UI/Main/MainViewController.m similarity index 100% rename from MainViewController.m rename to Classes/UI/Main/MainViewController.m diff --git a/NewLocalFileViewController.h b/Classes/UI/Main/NewLocalFileViewController.h similarity index 100% rename from NewLocalFileViewController.h rename to Classes/UI/Main/NewLocalFileViewController.h diff --git a/NewLocalFileViewController.m b/Classes/UI/Main/NewLocalFileViewController.m similarity index 100% rename from NewLocalFileViewController.m rename to Classes/UI/Main/NewLocalFileViewController.m diff --git a/NewRemoteFileViewController.h b/Classes/UI/Main/NewRemoteFileViewController.h similarity index 100% rename from NewRemoteFileViewController.h rename to Classes/UI/Main/NewRemoteFileViewController.h diff --git a/NewRemoteFileViewController.m b/Classes/UI/Main/NewRemoteFileViewController.m similarity index 100% rename from NewRemoteFileViewController.m rename to Classes/UI/Main/NewRemoteFileViewController.m diff --git a/PasswordViewController.h b/Classes/UI/Main/PasswordViewController.h similarity index 100% rename from PasswordViewController.h rename to Classes/UI/Main/PasswordViewController.h diff --git a/PasswordViewController.m b/Classes/UI/Main/PasswordViewController.m similarity index 100% rename from PasswordViewController.m rename to Classes/UI/Main/PasswordViewController.m diff --git a/RenameFileViewController.h b/Classes/UI/Main/RenameFileViewController.h similarity index 100% rename from RenameFileViewController.h rename to Classes/UI/Main/RenameFileViewController.h diff --git a/RenameFileViewController.m b/Classes/UI/Main/RenameFileViewController.m similarity index 100% rename from RenameFileViewController.m rename to Classes/UI/Main/RenameFileViewController.m diff --git a/RenameRemoteFileViewController.h b/Classes/UI/Main/RenameRemoteFileViewController.h similarity index 100% rename from RenameRemoteFileViewController.h rename to Classes/UI/Main/RenameRemoteFileViewController.h diff --git a/RenameRemoteFileViewController.m b/Classes/UI/Main/RenameRemoteFileViewController.m similarity index 100% rename from RenameRemoteFileViewController.m rename to Classes/UI/Main/RenameRemoteFileViewController.m diff --git a/SettingViewController.h b/Classes/UI/Main/SettingViewController.h similarity index 100% rename from SettingViewController.h rename to Classes/UI/Main/SettingViewController.h diff --git a/SettingViewController.m b/Classes/UI/Main/SettingViewController.m similarity index 100% rename from SettingViewController.m rename to Classes/UI/Main/SettingViewController.m diff --git a/MultiLineTableViewCellStyle2.h b/Classes/UI/MultiLineTableViewCellStyle2.h similarity index 100% rename from MultiLineTableViewCellStyle2.h rename to Classes/UI/MultiLineTableViewCellStyle2.h diff --git a/MultiLineTableViewCellStyle2.m b/Classes/UI/MultiLineTableViewCellStyle2.m similarity index 100% rename from MultiLineTableViewCellStyle2.m rename to Classes/UI/MultiLineTableViewCellStyle2.m diff --git a/PlainSectionHeader.h b/Classes/UI/PlainSectionHeader.h similarity index 100% rename from PlainSectionHeader.h rename to Classes/UI/PlainSectionHeader.h diff --git a/PlainSectionHeader.m b/Classes/UI/PlainSectionHeader.m similarity index 100% rename from PlainSectionHeader.m rename to Classes/UI/PlainSectionHeader.m diff --git a/MyKeePass.xcodeproj/project.pbxproj b/MyKeePass.xcodeproj/project.pbxproj index bed1023..8cfcb8b 100755 --- a/MyKeePass.xcodeproj/project.pbxproj +++ b/MyKeePass.xcodeproj/project.pbxproj @@ -330,7 +330,7 @@ F7DDD04C115DB13500FD4EE3 /* MyKeePassAppDelegate.h */, F7DDD04D115DB13500FD4EE3 /* MyKeePassAppDelegate.m */, ); - name = Classes; + path = Classes; sourceTree = ""; }; F7DDCFE0115DB13500FD4EE3 /* ThirdParty */ = { @@ -352,6 +352,7 @@ F7DDCFE2115DB13500FD4EE3 /* HTTP */, ); name = CocoaHttpServer; + path = ThirdParty/CocoaHttpServer; sourceTree = ""; }; F7DDCFE2115DB13500FD4EE3 /* HTTP */ = { @@ -366,7 +367,7 @@ F7DDCFE9115DB13500FD4EE3 /* HTTPAuthenticationRequest.h */, F7DDCFEA115DB13500FD4EE3 /* HTTPAuthenticationRequest.m */, ); - name = HTTP; + path = HTTP; sourceTree = ""; }; F7DDCFEB115DB13500FD4EE3 /* TCP */ = { @@ -375,7 +376,7 @@ F7DDCFEC115DB13500FD4EE3 /* AsyncSocket.h */, F7DDCFED115DB13500FD4EE3 /* AsyncSocket.m */, ); - name = TCP; + path = TCP; sourceTree = ""; }; F7DDCFEE115DB13500FD4EE3 /* Categories */ = { @@ -388,7 +389,7 @@ F7DDCFF3115DB13500FD4EE3 /* DDData.h */, F7DDCFF4115DB13500FD4EE3 /* DDData.m */, ); - name = Categories; + path = Categories; sourceTree = ""; }; F7DDCFF5115DB13500FD4EE3 /* Classes */ = { @@ -399,7 +400,7 @@ F7DDCFF8115DB13500FD4EE3 /* localhostAddresses.h */, F7DDCFF9115DB13500FD4EE3 /* localhostAddresses.m */, ); - name = Classes; + path = Classes; sourceTree = ""; }; F7DDCFFA115DB13500FD4EE3 /* ASIHTTPRequest */ = { @@ -422,6 +423,7 @@ F7DDD009115DB13500FD4EE3 /* ASIAuthenticationDialog.m */, ); name = ASIHTTPRequest; + path = ThirdParty/ASIHTTPRequest; sourceTree = ""; }; F7DDD00A115DB13500FD4EE3 /* Tapku */ = { @@ -437,6 +439,7 @@ F7DDD012115DB13500FD4EE3 /* TKLabelTextFieldCell.m */, ); name = Tapku; + path = ThirdParty/Tapku; sourceTree = ""; }; F7DDD013115DB13500FD4EE3 /* UI */ = { @@ -457,7 +460,7 @@ F7DDD018115DB13500FD4EE3 /* MultiLineTableViewCellStyle2.h */, F7DDD019115DB13500FD4EE3 /* MultiLineTableViewCellStyle2.m */, ); - name = UI; + path = UI; sourceTree = ""; }; F7DDD01A115DB13500FD4EE3 /* Kdb */ = { @@ -486,7 +489,7 @@ F7DDD02F115DB13500FD4EE3 /* OptionViewController.h */, F7DDD030115DB13500FD4EE3 /* OptionViewController.m */, ); - name = Kdb; + path = Kdb; sourceTree = ""; }; F7DDD031115DB13500FD4EE3 /* Main */ = { @@ -513,7 +516,7 @@ F7DDD044115DB13500FD4EE3 /* FileManagerOperation.h */, F7DDD045115DB13500FD4EE3 /* FileManagerOperation.m */, ); - name = Main; + path = Main; sourceTree = ""; }; F7DDD07F115DB15B00FD4EE3 /* images */ = { @@ -535,7 +538,7 @@ F7DDD08A115DB15B00FD4EE3 /* kdb.png */, F7DDD08B115DB15B00FD4EE3 /* kdbx.png */, ); - name = images; + path = images; sourceTree = ""; }; F7DDD0A4115DB21E00FD4EE3 /* Products */ = { @@ -575,7 +578,11 @@ isa = PBXProject; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "MyKeePass" */; compatibilityVersion = "Xcode 3.1"; + developmentRegion = English; hasScannedForEncodings = 1; + knownRegions = ( + en, + ); mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; projectDirPath = ""; projectReferences = ( diff --git a/MyKeePassAppDelegate.h b/MyKeePassAppDelegate.h deleted file mode 100644 index 5c20d6d..0000000 --- a/MyKeePassAppDelegate.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// MyKeePassAppDelegate.h -// MyKeePass -// -// Created by Qiang Yu on 3/3/10. -// Copyright Qiang Yu 2010. All rights reserved. -// - -#import -#import "MainViewController.h" -#import "KdbViewController.h" -#import "PasswordViewController.h" -#import "FileManager.h" - -@interface MyKeePassAppDelegate : NSObject { - FileManager * _fileManager; - - MainViewController * _mainView; - KdbViewController * _kdbView; - - UIViewController * _currentViewController; - - UIWindow *window; -} - -@property (nonatomic, retain) IBOutlet UIWindow *window; -@property (nonatomic, readonly) FileManager * _fileManager; -@property (nonatomic, retain) MainViewController * _mainView; -@property (nonatomic, retain) KdbViewController * _kdbView; -@property (nonatomic, retain) UIViewController * _currentViewController; - -+(MyKeePassAppDelegate *)delegate; -+(UIWindow *)getWindow; - --(void)showKdb; --(void)showMainView; --(BOOL)isEditable; -@end - diff --git a/MyKeePassAppDelegate.m b/MyKeePassAppDelegate.m deleted file mode 100644 index aa70fde..0000000 --- a/MyKeePassAppDelegate.m +++ /dev/null @@ -1,108 +0,0 @@ -// -// MyKeePassAppDelegate.m -// MyKeePass -// -// Created by Qiang Yu on 3/3/10. -// Copyright Qiang Yu 2010. All rights reserved. -// - -#import -#import "MyKeePassAppDelegate.h" - -@implementation MyKeePassAppDelegate -@synthesize window; -@synthesize _fileManager; -@synthesize _kdbView; -@synthesize _mainView; -@synthesize _currentViewController; - -- (void)applicationDidFinishLaunching:(UIApplication *)application { - //initialize the file manager - _fileManager = [[FileManager alloc]init]; - - //initialize the main view - _mainView = [[MainViewController alloc]init]; - - self._currentViewController = _mainView; - - [window addSubview:_mainView.view]; - [window makeKeyAndVisible]; -} - - -- (void)dealloc { - [_mainView release]; - [_kdbView release]; - [window release]; - [_fileManager release]; - [_currentViewController release]; - [super dealloc]; -} - --(void)showKdb{ - if(![_currentViewController isKindOfClass:[KdbViewController class]]){ - [_currentViewController.view removeFromSuperview]; - id kdbReader = _fileManager._kdbReader; - - if(!_kdbView){ - _kdbView = [[KdbViewController alloc]initWithGroup:[[kdbReader getKdbTree] getRoot]]; - } - - self._currentViewController = _kdbView; - - [window addSubview:_currentViewController.view]; - - CATransition *animation = [CATransition animation]; - [animation setDuration:0.5]; - [animation setType:kCATransitionFade]; - [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; - [[window layer] addAnimation:animation forKey:@"SwitchToKdbView"]; - } -} - --(void)showMainView{ - if(![_currentViewController isKindOfClass:[MainViewController class]]){ - [_currentViewController.view removeFromSuperview]; - _fileManager._kdbReader = nil; - self._kdbView = nil; - - if(!_mainView) - _mainView = [[MainViewController alloc]init]; - - self._currentViewController = _mainView; - - [window addSubview:_currentViewController.view]; - - CATransition *animation = [CATransition animation]; - [animation setDuration:0.5]; - [animation setType:kCATransitionFade]; - [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; - [[window layer] addAnimation:animation forKey:@"SwitchToMainView"]; - } -} - -- (void)applicationDidBecomeActive:(UIApplication *)application{ - if([_currentViewController isKindOfClass:[KdbViewController class]]){ - [_currentViewController.view removeFromSuperview]; - - PasswordViewController * lockWindow = [[PasswordViewController alloc] initWithFileName:_fileManager._filename remote:NO]; - lockWindow._isReopen = YES; - - self._currentViewController = lockWindow; - [window addSubview:lockWindow.view]; - [lockWindow release]; - } -} - --(BOOL)isEditable{ - return _fileManager._editable; -} - -+(MyKeePassAppDelegate *)delegate{ - return (MyKeePassAppDelegate *)[[UIApplication sharedApplication] delegate]; -} - -+(UIWindow *)getWindow{ - return ((MyKeePassAppDelegate *)[[UIApplication sharedApplication] delegate]).window; -} -@end diff --git a/Default.png b/images/Default.png similarity index 100% rename from Default.png rename to images/Default.png diff --git a/Icon-Small.png b/images/Icon-Small.png similarity index 100% rename from Icon-Small.png rename to images/Icon-Small.png diff --git a/Icon.png b/images/Icon.png similarity index 100% rename from Icon.png rename to images/Icon.png diff --git a/about.png b/images/about.png similarity index 100% rename from about.png rename to images/about.png diff --git a/add.png b/images/add.png similarity index 100% rename from add.png rename to images/add.png diff --git a/addPressed.png b/images/addPressed.png similarity index 100% rename from addPressed.png rename to images/addPressed.png diff --git a/files.png b/images/files.png similarity index 100% rename from files.png rename to images/files.png diff --git a/http.png b/images/http.png similarity index 100% rename from http.png rename to images/http.png diff --git a/kdb.png b/images/kdb.png similarity index 100% rename from kdb.png rename to images/kdb.png diff --git a/kdbx.png b/images/kdbx.png similarity index 100% rename from kdbx.png rename to images/kdbx.png diff --git a/options.png b/images/options.png similarity index 100% rename from options.png rename to images/options.png diff --git a/password.png b/images/password.png similarity index 100% rename from password.png rename to images/password.png diff --git a/passwordPressed.png b/images/passwordPressed.png similarity index 100% rename from passwordPressed.png rename to images/passwordPressed.png diff --git a/passwords.png b/images/passwords.png similarity index 100% rename from passwords.png rename to images/passwords.png diff --git a/unknown.png b/images/unknown.png similarity index 100% rename from unknown.png rename to images/unknown.png From 46eaf6e502b4db3260c1676c41b041e098e3ff13 Mon Sep 17 00:00:00 2001 From: jrroca Date: Sun, 8 May 2011 11:17:37 +0200 Subject: [PATCH 2/6] Added Dropbox SDK --- Classes/DropboxAPIKeys.h | 10 + Classes/MyKeePassAppDelegate.m | 8 + Classes/ThirdParty/DropboxSDK/DBAccountInfo.h | 28 + Classes/ThirdParty/DropboxSDK/DBAccountInfo.m | 61 ++ .../DropboxSDK/DBCreateAccountController.h | 36 + .../DropboxSDK/DBCreateAccountController.m | 437 ++++++++++ Classes/ThirdParty/DropboxSDK/DBError.h | 19 + Classes/ThirdParty/DropboxSDK/DBError.m | 11 + Classes/ThirdParty/DropboxSDK/DBLoadingView.h | 29 + Classes/ThirdParty/DropboxSDK/DBLoadingView.m | 166 ++++ .../ThirdParty/DropboxSDK/DBLoginController.h | 46 + .../ThirdParty/DropboxSDK/DBLoginController.m | 443 ++++++++++ Classes/ThirdParty/DropboxSDK/DBMetadata.h | 40 + Classes/ThirdParty/DropboxSDK/DBMetadata.m | 126 +++ Classes/ThirdParty/DropboxSDK/DBQuota.h | 23 + Classes/ThirdParty/DropboxSDK/DBQuota.m | 52 ++ Classes/ThirdParty/DropboxSDK/DBRequest.h | 71 ++ Classes/ThirdParty/DropboxSDK/DBRequest.m | 228 +++++ Classes/ThirdParty/DropboxSDK/DBRestClient.h | 138 +++ Classes/ThirdParty/DropboxSDK/DBRestClient.m | 800 ++++++++++++++++++ Classes/ThirdParty/DropboxSDK/DBSession.h | 44 + Classes/ThirdParty/DropboxSDK/DBSession.m | 117 +++ Classes/ThirdParty/DropboxSDK/DropboxSDK.h | 17 + Classes/ThirdParty/DropboxSDK/JSON/JSON.h | 50 ++ .../DropboxSDK/JSON/NSObject+SBJSON.h | 68 ++ .../DropboxSDK/JSON/NSObject+SBJSON.m | 53 ++ .../DropboxSDK/JSON/NSString+SBJSON.h | 58 ++ .../DropboxSDK/JSON/NSString+SBJSON.m | 55 ++ Classes/ThirdParty/DropboxSDK/JSON/SBJSON.h | 75 ++ Classes/ThirdParty/DropboxSDK/JSON/SBJSON.m | 212 +++++ .../ThirdParty/DropboxSDK/JSON/SBJsonBase.h | 86 ++ .../ThirdParty/DropboxSDK/JSON/SBJsonBase.m | 78 ++ .../ThirdParty/DropboxSDK/JSON/SBJsonParser.h | 87 ++ .../ThirdParty/DropboxSDK/JSON/SBJsonParser.m | 475 +++++++++++ .../ThirdParty/DropboxSDK/JSON/SBJsonWriter.h | 129 +++ .../ThirdParty/DropboxSDK/JSON/SBJsonWriter.m | 237 ++++++ .../MPOAuth/Crypto/Base64Transcoder.c | 230 +++++ .../MPOAuth/Crypto/Base64Transcoder.h | 36 + .../ThirdParty/DropboxSDK/MPOAuth/MPDebug.h | 13 + .../ThirdParty/DropboxSDK/MPOAuth/MPOAuth.h | 20 + .../DropboxSDK/MPOAuth/MPOAuthAPI.h | 86 ++ .../DropboxSDK/MPOAuth/MPOAuthAPI.m | 224 +++++ .../MPOAuth/MPOAuthAPIRequestLoader.h | 50 ++ .../MPOAuth/MPOAuthAPIRequestLoader.m | 220 +++++ .../MPOAuth/MPOAuthAuthenticationMethod.h | 30 + .../MPOAuth/MPOAuthAuthenticationMethod.m | 131 +++ .../MPOAuthAuthenticationMethodOAuth.h | 44 + .../MPOAuthAuthenticationMethodOAuth.m | 217 +++++ .../DropboxSDK/MPOAuth/MPOAuthConnection.h | 29 + .../DropboxSDK/MPOAuth/MPOAuthConnection.m | 58 ++ ...entiaIConcreteStore+KeychainAdditionsMac.m | 89 ++ ...redentialConcreteStore+KeychainAdditions.h | 18 + ...ialConcreteStore+KeychainAdditionsiPhone.m | 112 +++ .../MPOAuth/MPOAuthCredentialConcreteStore.h | 40 + .../MPOAuth/MPOAuthCredentialConcreteStore.m | 284 +++++++ .../MPOAuth/MPOAuthCredentialStore.h | 33 + .../MPOAuth/MPOAuthParameterFactory.h | 28 + .../MPOAuth/MPOAuthSignatureParameter.h | 28 + .../MPOAuth/MPOAuthSignatureParameter.m | 81 ++ .../DropboxSDK/MPOAuth/MPOAuthURLRequest.h | 32 + .../DropboxSDK/MPOAuth/MPOAuthURLRequest.m | 100 +++ .../DropboxSDK/MPOAuth/MPOAuthURLResponse.h | 20 + .../DropboxSDK/MPOAuth/MPOAuthURLResponse.m | 35 + .../MPOAuth/MPURLRequestParameter.h | 30 + .../MPOAuth/MPURLRequestParameter.m | 158 ++++ .../MPOAuth/NSString+URLEscapingAdditions.h | 21 + .../MPOAuth/NSString+URLEscapingAdditions.m | 60 ++ .../MPOAuth/NSURL+MPURLParameterAdditions.h | 21 + .../MPOAuth/NSURL+MPURLParameterAdditions.m | 98 +++ .../MPOAuth/NSURLResponse+Encoding.h | 14 + .../MPOAuth/NSURLResponse+Encoding.m | 27 + .../ThirdParty/DropboxSDK/NSString+Dropbox.h | 18 + .../ThirdParty/DropboxSDK/NSString+Dropbox.m | 23 + .../DropboxSDK/Resources/db_background.png | Bin 0 -> 4890 bytes .../Resources/db_create_account.png | Bin 0 -> 4600 bytes .../Resources/db_create_account@2x.png | Bin 0 -> 10348 bytes .../Resources/db_create_account_button.png | Bin 0 -> 6631 bytes .../db_create_account_button_down.png | Bin 0 -> 6286 bytes .../DropboxSDK/Resources/db_link_button.png | Bin 0 -> 4912 bytes .../Resources/db_link_button_down.png | Bin 0 -> 4814 bytes .../DropboxSDK/Resources/db_link_header.png | Bin 0 -> 6540 bytes .../Resources/db_link_header@2x.png | Bin 0 -> 16076 bytes .../DropboxSDK/Resources/db_logo.png | Bin 0 -> 19730 bytes .../DropboxSDK/Resources/db_logo@2x.png | Bin 0 -> 44022 bytes MyKeePass.xcodeproj/project.pbxproj | 299 +++++++ 85 files changed, 7670 insertions(+) create mode 100644 Classes/DropboxAPIKeys.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBAccountInfo.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBAccountInfo.m create mode 100644 Classes/ThirdParty/DropboxSDK/DBCreateAccountController.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBCreateAccountController.m create mode 100644 Classes/ThirdParty/DropboxSDK/DBError.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBError.m create mode 100644 Classes/ThirdParty/DropboxSDK/DBLoadingView.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBLoadingView.m create mode 100644 Classes/ThirdParty/DropboxSDK/DBLoginController.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBLoginController.m create mode 100644 Classes/ThirdParty/DropboxSDK/DBMetadata.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBMetadata.m create mode 100644 Classes/ThirdParty/DropboxSDK/DBQuota.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBQuota.m create mode 100644 Classes/ThirdParty/DropboxSDK/DBRequest.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBRequest.m create mode 100644 Classes/ThirdParty/DropboxSDK/DBRestClient.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBRestClient.m create mode 100644 Classes/ThirdParty/DropboxSDK/DBSession.h create mode 100644 Classes/ThirdParty/DropboxSDK/DBSession.m create mode 100644 Classes/ThirdParty/DropboxSDK/DropboxSDK.h create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/JSON.h create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/NSObject+SBJSON.h create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/NSObject+SBJSON.m create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/NSString+SBJSON.h create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/NSString+SBJSON.m create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/SBJSON.h create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/SBJSON.m create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/SBJsonBase.h create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/SBJsonBase.m create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/SBJsonParser.h create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/SBJsonParser.m create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/SBJsonWriter.h create mode 100644 Classes/ThirdParty/DropboxSDK/JSON/SBJsonWriter.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/Crypto/Base64Transcoder.c create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/Crypto/Base64Transcoder.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPDebug.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuth.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPI.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPI.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPIRequestLoader.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPIRequestLoader.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethod.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethod.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethodOAuth.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethodOAuth.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthConnection.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthConnection.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentiaIConcreteStore+KeychainAdditionsMac.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore+KeychainAdditions.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore+KeychainAdditionsiPhone.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialStore.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthParameterFactory.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthSignatureParameter.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthSignatureParameter.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLRequest.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLRequest.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLResponse.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLResponse.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPURLRequestParameter.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/MPURLRequestParameter.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/NSString+URLEscapingAdditions.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/NSString+URLEscapingAdditions.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/NSURL+MPURLParameterAdditions.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/NSURL+MPURLParameterAdditions.m create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/NSURLResponse+Encoding.h create mode 100644 Classes/ThirdParty/DropboxSDK/MPOAuth/NSURLResponse+Encoding.m create mode 100644 Classes/ThirdParty/DropboxSDK/NSString+Dropbox.h create mode 100644 Classes/ThirdParty/DropboxSDK/NSString+Dropbox.m create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_background.png create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_create_account.png create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_create_account@2x.png create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_create_account_button.png create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_create_account_button_down.png create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_link_button.png create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_link_button_down.png create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_link_header.png create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_link_header@2x.png create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_logo.png create mode 100644 Classes/ThirdParty/DropboxSDK/Resources/db_logo@2x.png diff --git a/Classes/DropboxAPIKeys.h b/Classes/DropboxAPIKeys.h new file mode 100644 index 0000000..a222671 --- /dev/null +++ b/Classes/DropboxAPIKeys.h @@ -0,0 +1,10 @@ +// +// DropboxAPIKeys.h +// MyKeePass +// +// Created by Jose Ramon Roca on 08/05/11. +// Copyright 2011 -. All rights reserved. +// + +#define DROPBOX_CONSUMER_KEY "" +#define DROPBOX_CONSUMER_SECRET "" \ No newline at end of file diff --git a/Classes/MyKeePassAppDelegate.m b/Classes/MyKeePassAppDelegate.m index aa70fde..2071f95 100644 --- a/Classes/MyKeePassAppDelegate.m +++ b/Classes/MyKeePassAppDelegate.m @@ -8,6 +8,8 @@ #import #import "MyKeePassAppDelegate.h" +#import "DropboxSDK.h" +#import "DropboxAPIKeys.h" @implementation MyKeePassAppDelegate @synthesize window; @@ -17,6 +19,12 @@ @implementation MyKeePassAppDelegate @synthesize _currentViewController; - (void)applicationDidFinishLaunching:(UIApplication *)application { + DBSession* dbSession = [[[DBSession alloc] + initWithConsumerKey:@DROPBOX_CONSUMER_KEY + consumerSecret:@DROPBOX_CONSUMER_SECRET] + autorelease]; + [DBSession setSharedSession:dbSession]; + //initialize the file manager _fileManager = [[FileManager alloc]init]; diff --git a/Classes/ThirdParty/DropboxSDK/DBAccountInfo.h b/Classes/ThirdParty/DropboxSDK/DBAccountInfo.h new file mode 100644 index 0000000..e54d544 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBAccountInfo.h @@ -0,0 +1,28 @@ +// +// DBAccountInfo.h +// DropboxSDK +// +// Created by Brian Smith on 5/3/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + + +#import "DBQuota.h" + +@interface DBAccountInfo : NSObject { + NSString* country; + NSString* displayName; + DBQuota* quota; + NSString* userId; + NSString* referralLink; +} + +- (id)initWithDictionary:(NSDictionary*)dict; + +@property (nonatomic, readonly) NSString* country; +@property (nonatomic, readonly) NSString* displayName; +@property (nonatomic, readonly) DBQuota* quota; +@property (nonatomic, readonly) NSString* userId; +@property (nonatomic, readonly) NSString* referralLink; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBAccountInfo.m b/Classes/ThirdParty/DropboxSDK/DBAccountInfo.m new file mode 100644 index 0000000..e30ffbc --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBAccountInfo.m @@ -0,0 +1,61 @@ +// +// DBAccountInfo.m +// DropboxSDK +// +// Created by Brian Smith on 5/3/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "DBAccountInfo.h" + + +@implementation DBAccountInfo + +- (id)initWithDictionary:(NSDictionary*)dict { + if ((self = [super init])) { + country = [[dict objectForKey:@"country"] retain]; + displayName = [[dict objectForKey:@"display_name"] retain]; + quota = [[DBQuota alloc] initWithDictionary:[dict objectForKey:@"quota_info"]]; + userId = [[[dict objectForKey:@"uid"] stringValue] retain]; + referralLink = [[dict objectForKey:@"referral_link"] retain]; + } + return self; +} + +- (void)dealloc { + [country release]; + [displayName release]; + [quota release]; + [userId release]; + [referralLink release]; + [super dealloc]; +} + +@synthesize country; +@synthesize displayName; +@synthesize quota; +@synthesize userId; +@synthesize referralLink; + + +#pragma mark NSCoding methods + +- (void)encodeWithCoder:(NSCoder*)coder { + [coder encodeObject:country forKey:@"country"]; + [coder encodeObject:displayName forKey:@"displayName"]; + [coder encodeObject:quota forKey:@"quota"]; + [coder encodeObject:userId forKey:@"userId"]; + [coder encodeObject:referralLink forKey:@"referralLink"]; +} + +- (id)initWithCoder:(NSCoder*)coder { + self = [super init]; + country = [[coder decodeObjectForKey:@"country"] retain]; + displayName = [[coder decodeObjectForKey:@"displayName"] retain]; + quota = [[coder decodeObjectForKey:@"quota"] retain]; + userId = [[coder decodeObjectForKey:@"userId"] retain]; + referralLink = [[coder decodeObjectForKey:@"referralLink"] retain]; + return self; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBCreateAccountController.h b/Classes/ThirdParty/DropboxSDK/DBCreateAccountController.h new file mode 100644 index 0000000..96ad7d7 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBCreateAccountController.h @@ -0,0 +1,36 @@ +// +// DBCreateAccountController.h +// DropboxSDK +// +// Created by Brian Smith on 5/20/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + + +@class DBLoadingView; +@class DBLoginController; +@class DBRestClient; + +@interface DBCreateAccountController : UIViewController { + BOOL hasCreatedAccount; + DBLoginController* loginController; + DBRestClient* restClient; + + UITableView* tableView; + UIView* headerView; + UITableViewCell* firstNameCell; + UITextField* firstNameField; + UITableViewCell* lastNameCell; + UITextField* lastNameField; + UITableViewCell* emailCell; + UITextField* emailField; + UITableViewCell* passwordCell; + UITextField* passwordField; + UIView* footerView; + UIActivityIndicatorView* activityIndicator; + DBLoadingView* loadingView; +} + +@property (nonatomic, assign) DBLoginController* loginController; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBCreateAccountController.m b/Classes/ThirdParty/DropboxSDK/DBCreateAccountController.m new file mode 100644 index 0000000..9447ede --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBCreateAccountController.m @@ -0,0 +1,437 @@ +// +// DBCreateAccountController.m +// DropboxSDK +// +// Created by Brian Smith on 5/20/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "DBCreateAccountController.h" +#import "DBLoadingView.h" +#import "DBLoginController.h" +#import "DBRestClient.h" + + +enum { + kRowFirstName, + kRowLastName, + kRowEmail, + kRowPassword, + kRowCount +}; + +#define kTextFieldFrame CGRectMake(108, 11, 182, 24) +#define CLEAR_OBJ(FIELD) [FIELD release]; FIELD = nil; + + +@interface DBCreateAccountController () + +- (void)didPressCreateAccount; +- (void)setWorking:(BOOL)working; +- (void)updateActionButton; +- (void)errorWithTitle:(NSString*)title message:(NSString*)message; + +@property (nonatomic, readonly) DBRestClient* restClient; + +@end + + +@implementation DBCreateAccountController + +- (void)viewDidLoad { + [super viewDidLoad]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + self.title = @"Create Account"; + UIBarButtonItem* saveItem = + [[[UIBarButtonItem alloc] + initWithTitle:@"Create" style:UIBarButtonItemStyleDone + target:self action:@selector(didPressCreateAccount)] + autorelease]; + self.navigationItem.rightBarButtonItem = saveItem; + + } else { + self.title = @"Create Dropbox Account"; + } + + UIImageView* background = + [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"db_background.png"]]; + background.frame = self.view.bounds; + background.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.view addSubview:background]; + [background release]; + + CGRect tableFrame = self.view.bounds; + tableFrame.size.width = 320; + tableFrame.origin.x = floor(self.view.bounds.size.width/2 - tableFrame.size.width/2); + tableView = + [[UITableView alloc] initWithFrame:tableFrame style:UITableViewStyleGrouped]; + tableView.autoresizingMask = + UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | + UIViewAutoresizingFlexibleRightMargin; + tableView.backgroundColor = [UIColor clearColor]; + if ([tableView respondsToSelector:@selector(setBackgroundView:)]) { + [tableView performSelector:@selector(setBackgroundView:) withObject:nil]; + } + tableView.scrollEnabled = NO; + tableView.dataSource = self; + tableView.delegate = self; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + UIView* tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 64)]; + tableView.tableHeaderView = tableHeaderView; + [tableHeaderView release]; + } + [self.view addSubview:tableView]; +} + +- (void)releaseViews { + CLEAR_OBJ(tableView); + CLEAR_OBJ(footerView); + CLEAR_OBJ(firstNameCell); + CLEAR_OBJ(firstNameField); + CLEAR_OBJ(lastNameCell); + CLEAR_OBJ(lastNameField); + CLEAR_OBJ(emailCell); + CLEAR_OBJ(emailField); + CLEAR_OBJ(passwordCell); + CLEAR_OBJ(passwordField); + CLEAR_OBJ(footerView); + CLEAR_OBJ(activityIndicator); + CLEAR_OBJ(loadingView); +} + +- (void)viewDidUnload { + [super viewDidUnload]; + [self releaseViews]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [self updateActionButton]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [firstNameField becomeFirstResponder]; +} + + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + return interfaceOrientation == UIInterfaceOrientationPortrait; + } else { + return YES; + } +} + + +- (void)dealloc { + [self releaseViews]; + [restClient release]; + [super dealloc]; +} + + +@synthesize loginController; + + +- (IBAction)didPressCreateAccount { + if ([firstNameField.text length] == 0) { + [self errorWithTitle:@"First Name Required" message:@"Please enter your first name."]; + return; + } else if ([lastNameField.text length] == 0) { + [self errorWithTitle:@"Last Name Required" message:@"Please enter your last name."]; + return; + } else if ([emailField.text length] == 0) { + [self errorWithTitle:@"Email Address Required" message:@"Please enter your email address."]; + return; + } else if ([passwordField.text length] == 0) { + [self errorWithTitle:@"Password Required" message:@"Please enter your desired password"]; + return; + } + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + [firstNameField resignFirstResponder]; + [lastNameField resignFirstResponder]; + [emailField resignFirstResponder]; + [passwordField resignFirstResponder]; + } + [self setWorking:YES]; + + if (!hasCreatedAccount) { + [self.restClient createAccount:emailField.text password:passwordField.text + firstName:firstNameField.text lastName:lastNameField.text]; + } else { + [self.restClient loginWithEmail:emailField.text password:passwordField.text]; + } +} + + +#pragma mark UITextFieldDelegate + +- (BOOL)textFieldShouldReturn:(UITextField*)textField { + if (textField == firstNameField) { + [lastNameField becomeFirstResponder]; + } else if (textField == lastNameField) { + [emailField becomeFirstResponder]; + } else if (textField == emailField) { + [passwordField becomeFirstResponder]; + } else { + [self didPressCreateAccount]; + } + + return YES; +} + + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range +replacementString:(NSString *)string { + // Update the button after the replacement has been made + [self performSelector:@selector(updateActionButton) withObject:nil afterDelay:0]; + return YES; +} + + +#pragma mark UITableViewDataSource methods + +- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section { + return kRowCount; +} + +- (UITableViewCell*)newCellWithLabel:(NSString*)label textField:(UITextField*)textField { + + UITableViewCell* cell = + [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + cell.textLabel.text = label; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + textField.frame = kTextFieldFrame; + textField.borderStyle = UITextBorderStyleNone; + textField.delegate = self; + [cell.contentView addSubview:textField]; + + return cell; +} + +- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath { + switch ([indexPath row]) { + case kRowFirstName: + if (!firstNameCell) { + firstNameField = [UITextField new]; + firstNameField.placeholder = @"John"; + firstNameField.returnKeyType = UIReturnKeyNext; + firstNameField.autocorrectionType = UITextAutocorrectionTypeNo; + firstNameCell = [self newCellWithLabel:@"First Name" textField:firstNameField]; + } + return firstNameCell; + case kRowLastName: + if (!lastNameCell) { + lastNameField = [UITextField new]; + lastNameField.placeholder = @"Appleseed"; + lastNameField.returnKeyType = UIReturnKeyNext; + lastNameField.autocorrectionType = UITextAutocorrectionTypeNo; + lastNameCell = [self newCellWithLabel:@"Last Name" textField:lastNameField]; + } + return lastNameCell; + case kRowEmail: + if (!emailCell) { + emailField = [UITextField new]; + emailField.placeholder = @"example@gmail.com"; + emailField.keyboardType = UIKeyboardTypeEmailAddress; + emailField.autocapitalizationType = UITextAutocapitalizationTypeNone; + emailField.returnKeyType = UIReturnKeyNext; + emailField.autocorrectionType = UITextAutocorrectionTypeNo; + emailCell = [self newCellWithLabel:@"Email" textField:emailField]; + } + return emailCell; + case kRowPassword: + if (!passwordCell) { + passwordField = [UITextField new]; + passwordField.secureTextEntry = YES; + passwordField.returnKeyType = UIReturnKeyDone; + passwordField.placeholder = @"Required"; + passwordCell = [self newCellWithLabel:@"Password" textField:passwordField]; + } + return passwordCell; + default: + return nil; + } +} + + +#pragma mark UITableViewDelegate methods + +- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath { + switch ([indexPath row]) { + case kRowFirstName: + [firstNameField becomeFirstResponder]; + return; + case kRowLastName: + [lastNameField becomeFirstResponder]; + return; + case kRowEmail: + [emailField becomeFirstResponder]; + return; + case kRowPassword: + [passwordField becomeFirstResponder]; + return; + } +} + +- (CGFloat)tableView:(UITableView*)tableView heightForHeaderInSection:(NSInteger)section { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) return 0; + + return 53; +} + +- (UIView*)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) return nil; + + if (headerView == nil) { + headerView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"db_link_header.png"]]; + headerView.contentMode = UIViewContentModeCenter; + } + return headerView; +} + +- (CGFloat)tableView:(UITableView*)tableView heightForFooterInSection:(NSInteger)section { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) return 0; + + return 80; +} + +- (UIView*)tableView:(UITableView*)tableView viewForFooterInSection:(NSInteger)section { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) return nil; + + if (footerView == nil) { + footerView = [UIView new]; + footerView.backgroundColor = [UIColor clearColor]; + + UIButton* createButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [createButton setImage:[UIImage imageNamed:@"db_create_account_button.png"] + forState:UIControlStateNormal]; + [createButton setImage:[UIImage imageNamed:@"db_create_account_button_down.png"] + forState:UIControlStateHighlighted]; + [createButton addTarget:self action:@selector(didPressCreateAccount) + forControlEvents:UIControlEventTouchUpInside]; + [createButton sizeToFit]; + CGRect buttonFrame = createButton.frame; + buttonFrame.origin.x = 320 - buttonFrame.size.width - 8; + buttonFrame.origin.y = 8; + createButton.frame = buttonFrame; + [footerView addSubview:createButton]; + + activityIndicator = [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + CGRect activityFrame = activityIndicator.frame; + activityFrame.origin.x = 320 - activityFrame.size.width - 21; + activityFrame.origin.y = 17; + activityIndicator.frame = activityFrame; + [footerView addSubview:activityIndicator]; + } + return footerView; +} + + +#pragma mark DBRestClientDelegate methods + +- (void)restClientCreatedAccount:(DBRestClient*)client { + hasCreatedAccount = YES; + [self.restClient loginWithEmail:emailField.text password:passwordField.text]; +} + + +- (void)restClient:(DBRestClient*)client createAccountFailedWithError:(NSError*)error { + [self setWorking:NO]; + + NSString* message = @"An unknown error occured."; + if ([error.domain isEqual:NSURLErrorDomain]) { + message = @"There was an error connecting to Dropbox."; + } else { + NSObject* errorResponse = [[error userInfo] objectForKey:@"error"]; + if ([errorResponse isKindOfClass:[NSString class]]) { + message = (NSString*)errorResponse; + } else if ([errorResponse isKindOfClass:[NSDictionary class]]) { + NSDictionary* errorDict = (NSDictionary*)errorResponse; + message = [errorDict objectForKey:[[errorDict allKeys] objectAtIndex:0]]; + } + } + [self errorWithTitle:@"Create Account Failed" message:message]; +} + + +- (void)restClientDidLogin:(DBRestClient*)client { + [self setWorking:NO]; + [self.navigationController.parentViewController dismissModalViewControllerAnimated:YES]; + [loginController.delegate loginControllerDidLogin:loginController]; +} + + +- (void)restClient:(DBRestClient*)client loginFailedWithError:(NSError*)error { + [self setWorking:NO]; + // Need to make sure they don't change the email or password if create account succeeded + // but login failed + emailField.enabled = NO; + passwordField.enabled = NO; + [self errorWithTitle:@"Login Failed" message:@"Please try again."]; +} + + +#pragma mark private methods + +- (void)setWorking:(BOOL)working { + self.view.userInteractionEnabled = !working; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + if (working) { + loadingView = [[DBLoadingView alloc] initWithTitle:@"Creating Account"]; + [loadingView show]; + } else { + [loadingView dismissAnimated:NO]; + [loadingView release]; + loadingView = nil; + } + [self updateActionButton]; + } else { + if (working) { + [activityIndicator startAnimating]; + } else { + [activityIndicator stopAnimating]; + } + } +} + + +- (void)updateActionButton { + self.navigationItem.rightBarButtonItem.enabled = + [firstNameField.text length] > 0 && + [lastNameField.text length] > 0 && + [emailField.text length] > 0 && + [passwordField.text length] > 0 && + !loadingView; +} + + +- (void)errorWithTitle:(NSString*)title message:(NSString*)message { + [[[[UIAlertView alloc] + initWithTitle:title message:message delegate:nil + cancelButtonTitle:@"OK" otherButtonTitles:nil] + autorelease] + show]; +} + + +- (DBRestClient*)restClient { + if (restClient == nil) { + restClient = [[DBRestClient alloc] initWithSession:[DBSession sharedSession]]; + restClient.delegate = self; + } + return restClient; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBError.h b/Classes/ThirdParty/DropboxSDK/DBError.h new file mode 100644 index 0000000..4f87be3 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBError.h @@ -0,0 +1,19 @@ +// +// DBError.h +// DropboxSDK +// +// Created by Brian Smith on 7/21/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +/* This file contains error codes and the dropbox error domain */ + +extern NSString* DBErrorDomain; + +// Error codes in the dropbox.com domain represent the HTTP status code if less than 1000 +typedef enum { + DBErrorNone = 0, + DBErrorGenericError = 1000, + DBErrorFileNotFound, + DBErrorInsufficientDiskSpace, +} DBErrorCode; diff --git a/Classes/ThirdParty/DropboxSDK/DBError.m b/Classes/ThirdParty/DropboxSDK/DBError.m new file mode 100644 index 0000000..790fc0b --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBError.m @@ -0,0 +1,11 @@ +// +// DBError.m +// DropboxSDK +// +// Created by Brian Smith on 7/21/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "DBError.h" + +NSString* DBErrorDomain = @"dropbox.com"; diff --git a/Classes/ThirdParty/DropboxSDK/DBLoadingView.h b/Classes/ThirdParty/DropboxSDK/DBLoadingView.h new file mode 100644 index 0000000..242fcfa --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBLoadingView.h @@ -0,0 +1,29 @@ +// +// DBLoadingView.h +// DropboxSDK +// +// Created by Brian Smith on 6/30/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import + + +@interface DBLoadingView : UIView { + UIInterfaceOrientation orientation; + UILabel* titleLabel; + UIActivityIndicatorView* activityIndicator; + UIImageView* imageView; +} + +- (id)initWithTitle:(NSString*)title; + +- (void)setImage:(UIImage*)image; +- (void)setOrientation:(UIInterfaceOrientation)orientation; + +- (void)show; +- (void)dismissAnimated:(BOOL)animated; + +//@property (nonatomic, retain) NSString* title; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBLoadingView.m b/Classes/ThirdParty/DropboxSDK/DBLoadingView.m new file mode 100644 index 0000000..4fa9fa6 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBLoadingView.m @@ -0,0 +1,166 @@ +// +// DBLoadingView.m +// DropboxSDK +// +// Created by Brian Smith on 6/30/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "DBLoadingView.h" + + +#define kPadding 10 + + +@interface DBLoadingView () + +- (CGRect)beveledBoxFrame; + +@end + + +@implementation DBLoadingView + +- (id)init { + return [self initWithTitle:nil]; +} + +- (id)initWithTitle:(NSString*)theTitle { + CGRect frame = [[UIApplication sharedApplication] keyWindow].frame; + if ((self = [super initWithFrame:frame])) { + self.backgroundColor = [UIColor clearColor]; + self.userInteractionEnabled = NO; + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + activityIndicator = + [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + [self addSubview:activityIndicator]; + + imageView = [[UIImageView alloc] init]; + [self addSubview:imageView]; + + titleLabel = [UILabel new]; + titleLabel.text = theTitle; + titleLabel.textColor = [UIColor whiteColor]; + titleLabel.backgroundColor = [UIColor clearColor]; + titleLabel.textAlignment = UITextAlignmentCenter; + [self addSubview:titleLabel]; + } + return self; +} + +- (void)drawRect:(CGRect)rect { + CGRect contentFrame = [self beveledBoxFrame]; + + CGContextRef context = UIGraphicsGetCurrentContext(); + CGFloat fillColor[] = { 0, 0, 0, 128.0/255 }; + CGContextSetFillColor(context, fillColor); + CGFloat radius = 6; + CGContextMoveToPoint(context, contentFrame.origin.x + radius, contentFrame.origin.y); + CGContextAddArcToPoint(context, + CGRectGetMaxX(contentFrame), contentFrame.origin.y, + CGRectGetMaxX(contentFrame), CGRectGetMaxY(contentFrame), radius); + CGContextAddArcToPoint(context, + CGRectGetMaxX(contentFrame), CGRectGetMaxY(contentFrame), + contentFrame.origin.x, CGRectGetMaxY(contentFrame), radius); + CGContextAddArcToPoint(context, + contentFrame.origin.x, CGRectGetMaxY(contentFrame), + contentFrame.origin.x, contentFrame.origin.y, radius); + CGContextAddArcToPoint(context, + contentFrame.origin.x, contentFrame.origin.y, + CGRectGetMaxX(contentFrame), contentFrame.origin.y, radius); + CGContextClosePath(context); + CGContextFillPath(context); +} + +- (void)layoutSubviews { + CGRect contentFrame = [self beveledBoxFrame]; + + activityIndicator.center = CGPointMake( + floor(contentFrame.origin.x + contentFrame.size.width/2), + floor(contentFrame.origin.y + contentFrame.size.height/2) - kPadding); + + CGFloat titleLeading = titleLabel.font.leading; + CGRect titleFrame = CGRectMake( + contentFrame.origin.x + kPadding, + CGRectGetMaxY(contentFrame) - 2*kPadding - titleLeading, + contentFrame.size.width - 2*kPadding, titleLeading); + titleLabel.frame = titleFrame; + + CGRect imageFrame = imageView.frame; + imageFrame.origin.x = contentFrame.origin.x + floor(contentFrame.size.width/2 - imageFrame.size.width/2); + imageFrame.origin.y = contentFrame.origin.y + floor(contentFrame.size.height/2 - imageFrame.size.height/2); + imageView.frame = imageFrame; +} + +- (void)dealloc { + [activityIndicator release]; + [imageView release]; + [titleLabel release]; + [super dealloc]; +} + +- (void)setImage:(UIImage*)image { + imageView.image = image; + [imageView sizeToFit]; + + [self setNeedsLayout]; +} + +- (void)setOrientation:(UIInterfaceOrientation)newOrientation { + if (newOrientation == orientation) return; + orientation = newOrientation; + + if (orientation == UIInterfaceOrientationPortrait) { + self.transform = CGAffineTransformIdentity; + } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) { + self.transform = CGAffineTransformMakeRotation(M_PI); + } else if (orientation == UIInterfaceOrientationLandscapeRight) { + self.transform = CGAffineTransformMakeRotation(M_PI/2); + } else { + self.transform = CGAffineTransformMakeRotation(-M_PI/2); + } +} + +- (void)show { + if (!imageView.image) { + // Only show activity indicator when we don't have an image + [activityIndicator startAnimating]; + } + UIWindow* window = [[UIApplication sharedApplication] keyWindow]; + self.frame = window.frame; + [window addSubview:self]; +} + +- (void)finishDismiss { + [activityIndicator stopAnimating]; + [self removeFromSuperview]; +} + +- (void)dismissAnimated:(BOOL)animated { + if (!animated) { + [self finishDismiss]; + } else { + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:0.8]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(finishDismiss)]; + + self.alpha = 0; + + [UIView commitAnimations]; + } +} + +- (CGRect)beveledBoxFrame { + CGSize contentSize = self.bounds.size; + CGSize boxSize = CGSizeMake(160, 160); + CGFloat yOffset = UIInterfaceOrientationIsPortrait(orientation) ? 18 : 0; + return CGRectMake( + floor(contentSize.width/2 - boxSize.width/2), + floor(contentSize.height/2 - boxSize.height/2) + yOffset, + boxSize.width, boxSize.height); +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBLoginController.h b/Classes/ThirdParty/DropboxSDK/DBLoginController.h new file mode 100644 index 0000000..d445482 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBLoginController.h @@ -0,0 +1,46 @@ +// +// DBLoginController.h +// DropboxSDK +// +// Created by Brian Smith on 5/20/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + + +@class DBLoadingView; +@class DBRestClient; +@protocol DBLoginControllerDelegate; + +@interface DBLoginController : UIViewController { + BOOL hasAppeared; // Used to track whether the view totally onscreen + id delegate; + + UITableView* tableView; + UILabel* descriptionLabel; + UITableViewCell* emailCell; + UITextField* emailField; + UITableViewCell* passwordCell; + UITextField* passwordField; + UIView* footerView; + + DBLoadingView* loadingView; + UIActivityIndicatorView* activityIndicator; + + DBRestClient* restClient; +} + +- (void)presentFromController:(UIViewController*)controller; + +@property (nonatomic, assign) id delegate; + +@end + + +// This controller tells the delegate whether the user sucessfully logged in or not +// The login controller will dismiss itself +@protocol DBLoginControllerDelegate + +- (void)loginControllerDidLogin:(DBLoginController*)controller; +- (void)loginControllerDidCancel:(DBLoginController*)controller; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBLoginController.m b/Classes/ThirdParty/DropboxSDK/DBLoginController.m new file mode 100644 index 0000000..628b250 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBLoginController.m @@ -0,0 +1,443 @@ +// +// DBLoginController.m +// DropboxSDK +// +// Created by Brian Smith on 5/20/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "DBLoadingView.h" +#import "DBLoginController.h" +#import "DBCreateAccountController.h" +#import "DBRestClient.h" + + +#define kTextFieldFrame CGRectMake(100, 11, 190, 24) + + +@interface DBLoginController () + +- (void)didPressLogin; +- (void)didPressCreateAccount; +- (void)setWorking:(BOOL)working; +- (void)errorWithTitle:(NSString*)title message:(NSString*)message; +- (void)updateActionButton; + +@property (nonatomic, readonly) DBRestClient* restClient; + +@end + + +@implementation DBLoginController + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + return interfaceOrientation == UIInterfaceOrientationPortrait; + } else { + return YES; + } +} + +- (void)viewDidLoad { + [super viewDidLoad]; + UIBarButtonItem* cancelItem = + [[[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemCancel + target:self action:@selector(didPressCancel)] + autorelease]; + + self.title = @"Link Account"; + + UIImageView* background = + [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"db_background.png"]]; + background.frame = self.view.bounds; + background.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.view addSubview:background]; + [background release]; + + CGRect tableFrame = self.view.bounds; + tableFrame.size.width = 320; + tableFrame.origin.x = floor(self.view.bounds.size.width/2 - tableFrame.size.width/2); + tableView = + [[UITableView alloc] initWithFrame:tableFrame style:UITableViewStyleGrouped]; + tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | + UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; + tableView.backgroundColor = [UIColor clearColor]; + tableView.scrollEnabled = NO; + tableView.delegate = self; + tableView.dataSource = self; + if ([tableView respondsToSelector:@selector(setBackgroundView:)]) { + [tableView performSelector:@selector(setBackgroundView:) withObject:nil]; + } + + UIView* headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 152)]; + UIImageView* logo = + [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"db_logo.png"]]; + logo.contentMode = UIViewContentModeCenter; + logo.autoresizingMask = UIViewAutoresizingFlexibleWidth; + [logo sizeToFit]; + logo.center = headerView.center; + CGRect logoFrame = logo.frame; + logoFrame.origin.y += 7; + logo.frame = logoFrame; + [headerView addSubview:logo]; + tableView.tableHeaderView = headerView; + [headerView release]; + [logo release]; + + UIButton* createAccountButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [createAccountButton + setImage:[UIImage imageNamed:@"db_create_account.png"] + forState:UIControlStateNormal]; + [createAccountButton + addTarget:self action:@selector(didPressCreateAccount) + forControlEvents:UIControlEventTouchUpInside]; + CGFloat createAccountHeight = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone ? 36 : 44; + createAccountButton.frame = CGRectMake(0, 0, 264, createAccountHeight); + tableView.tableFooterView = createAccountButton; + + [self.view addSubview:tableView]; + + self.navigationItem.leftBarButtonItem = cancelItem; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + self.navigationItem.rightBarButtonItem = + [[[UIBarButtonItem alloc] + initWithTitle:@"Link" style:UIBarButtonItemStyleDone + target:self action:@selector(didPressLogin)] + autorelease]; + self.navigationItem.backBarButtonItem = + [[[UIBarButtonItem alloc] + initWithTitle:@"Link" style:UIBarButtonItemStylePlain target:nil action:nil] + autorelease]; + } +} + + +- (void)releaseViews { + [tableView release]; + tableView = nil; + [descriptionLabel release]; + descriptionLabel = nil; + [emailCell release]; + emailCell = nil; + [emailField release]; + emailField = nil; + [passwordCell release]; + passwordCell = nil; + [passwordField release]; + passwordField = nil; + [footerView release]; + footerView = nil; + [activityIndicator release]; + activityIndicator = nil; +} + +- (void)viewDidUnload { + [super viewDidUnload]; + [self releaseViews]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + [self updateActionButton]; +} + +- (void)dealloc { + [loadingView release]; + [restClient release]; + [self releaseViews]; + [super dealloc]; +} + + +- (void)presentFromController:(UIViewController*)controller { + UINavigationController* navController = + [[[UINavigationController alloc] initWithRootViewController:self] autorelease]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + navController.modalPresentationStyle = UIModalPresentationFormSheet; + } + [controller presentModalViewController:navController animated:YES]; +} + + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + hasAppeared = NO; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + hasAppeared = YES; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && + UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) { + + [emailField becomeFirstResponder]; + } +} + + +@synthesize delegate; + + +- (void)didPressLogin { + if ([emailField.text length] == 0) { + [self errorWithTitle:@"Email Required" message:@"Please enter your email."]; + return; + } else if ([passwordField.text length] == 0) { + [self errorWithTitle:@"Password Required" message:@"Please enter you password."]; + return; + } + + [emailField resignFirstResponder]; + [passwordField resignFirstResponder]; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + [tableView setContentOffset:CGPointZero animated:YES]; + } + [self setWorking:YES]; + + [self.restClient loginWithEmail:emailField.text password:passwordField.text]; +} + + +- (void)didPressCreateAccount { + DBCreateAccountController* createAccountController = + [[DBCreateAccountController new] autorelease]; + createAccountController.loginController = self; + [self.navigationController pushViewController:createAccountController animated:YES]; +} + + +- (void)didPressCancel { + [self setWorking:NO]; + [self.navigationController.parentViewController dismissModalViewControllerAnimated:YES]; + [delegate loginControllerDidCancel:self]; +} + + +#pragma mark UITextFieldDelegate methods + +- (BOOL)textFieldShouldBeginEditing:(UITextField*)textField { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + [tableView setContentOffset:CGPointMake(0, 150) animated:hasAppeared]; + } + return YES; +} + +- (BOOL)textFieldShouldReturn:(UITextField*)textField { + if (textField == emailField) { + [passwordField becomeFirstResponder]; + } else { + [self didPressLogin]; + } + + return YES; +} + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range +replacementString:(NSString *)string { + [self performSelector:@selector(updateActionButton) withObject:nil afterDelay:0]; + return YES; +} + + +#pragma mark UITableViewDataSource methods + +- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section { + return section == 0 ? 2 : 0; +} + +- (UITableViewCell*)newCellWithTitle:(NSString*)title textField:(UITextField*)textField { + UITableViewCell* cell = + [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + cell.textLabel.text = title; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + textField.frame = kTextFieldFrame; + textField.borderStyle = UITextBorderStyleNone; + textField.delegate = self; + [cell.contentView addSubview:textField]; + return cell; +} + +- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath { + if ([indexPath row] == 0) { + if (!emailCell) { + emailField = [UITextField new]; + emailField.placeholder = @"example@gmail.com"; + emailField.keyboardType = UIKeyboardTypeEmailAddress; + emailField.returnKeyType = UIReturnKeyNext; + emailField.autocapitalizationType = UITextAutocapitalizationTypeNone; + emailField.autocorrectionType = UITextAutocorrectionTypeNo; + emailCell = [self newCellWithTitle:@"Email" textField:emailField]; + } + return emailCell; + } else { + if (!passwordCell) { + passwordField = [UITextField new]; + passwordField.placeholder = @"Required"; + passwordField.secureTextEntry = YES; + passwordField.returnKeyType = UIReturnKeyDone; + passwordCell = [self newCellWithTitle:@"Password" textField:passwordField]; + } + return passwordCell; + } +} + + +#pragma mark UITableViewDelegate methods + +- (CGFloat)tableView:(UITableView*)tableView heightForHeaderInSection:(NSInteger)section { + if (section != 0) return 0; + + return 53; +} + +- (UIView*)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section { + if (section != 0) return nil; + + if (!descriptionLabel) { + descriptionLabel = + [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"db_link_header.png"]]; + descriptionLabel.contentMode = UIViewContentModeCenter; +/* + descriptionLabel = [UILabel new]; + descriptionLabel.backgroundColor = [UIColor clearColor]; + descriptionLabel.textColor = [UIColor whiteColor]; + descriptionLabel.font = [UIFont systemFontOfSize:15]; + descriptionLabel.textAlignment = UITextAlignmentCenter; + descriptionLabel.text = + @"Linking will allow this app to access\nand modify files in your Dropbox"; + descriptionLabel.numberOfLines = 2; +*/ + } + return descriptionLabel; +} + +- (CGFloat)tableView:(UITableView*)tableView heightForFooterInSection:(NSInteger)section { + return UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? 80 : 0; +} + +- (UIView*)tableView:(UITableView*)tableView viewForFooterInSection:(NSInteger)section { + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) return nil; + + if (footerView == nil) { + footerView = [[UIView alloc] init]; + footerView.backgroundColor = [UIColor clearColor]; + UIButton* linkButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [linkButton addTarget:self action:@selector(didPressLogin) + forControlEvents:UIControlEventTouchUpInside]; + [linkButton setImage:[UIImage imageNamed:@"db_link_button.png"] + forState:UIControlStateNormal]; + [linkButton setImage:[UIImage imageNamed:@"db_link_button_down.png"] + forState:UIControlStateHighlighted]; + [linkButton sizeToFit]; + CGRect linkFrame = linkButton.frame; + linkFrame.origin.x = 320 - linkFrame.size.width - 8; + linkFrame.origin.y = 8; + linkButton.frame = linkFrame; + [footerView addSubview:linkButton]; + + activityIndicator = [[UIActivityIndicatorView alloc] + initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + CGRect activityFrame = activityIndicator.frame; + activityFrame.origin.x = 320 - activityFrame.size.width - 21; + activityFrame.origin.y = 17; + activityIndicator.frame = activityFrame; + [footerView addSubview:activityIndicator]; + } + return footerView; +} + +- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath { + if ([indexPath row] == 0) { + [emailField becomeFirstResponder]; + } else { + [passwordField becomeFirstResponder]; + } +} + + +#pragma mark DBRestClient methods + +- (void)restClientDidLogin:(DBRestClient*)client { + [self setWorking:NO]; + [self.parentViewController dismissModalViewControllerAnimated:YES]; + [delegate loginControllerDidLogin:self]; +} + + +- (void)restClient:(DBRestClient*)client loginFailedWithError:(NSError*)error { + [self setWorking:NO]; + + NSString* message; + if ([error.domain isEqual:NSURLErrorDomain]) { + message = @"There was an error connecting to Dropbox."; + } else { + NSObject* errorResponse = [[error userInfo] objectForKey:@"error"]; + if ([errorResponse isKindOfClass:[NSString class]]) { + message = (NSString*)errorResponse; + } else if ([errorResponse isKindOfClass:[NSDictionary class]]) { + NSDictionary* errorDict = (NSDictionary*)errorResponse; + message = [errorDict objectForKey:[[errorDict allKeys] objectAtIndex:0]]; + } else { + message = @"An unknown error has occurred."; + } + } + [self errorWithTitle:@"Unable to Login" message:message]; +} + + +#pragma mark private methods + +- (void)setWorking:(BOOL)working { + self.view.userInteractionEnabled = !working; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + if (working) { + loadingView = [[DBLoadingView alloc] initWithTitle:@"Linking"]; + [loadingView show]; + } else { + [loadingView dismissAnimated:NO]; + [loadingView release]; + loadingView = nil; + } + [self updateActionButton]; + } else { + if (working) { + [activityIndicator startAnimating]; + } else { + [activityIndicator stopAnimating]; + } + } +} + + +- (void)errorWithTitle:(NSString*)title message:(NSString*)message { + [[[[UIAlertView alloc] + initWithTitle:title message:message delegate:nil + cancelButtonTitle:@"OK" otherButtonTitles:nil] + autorelease] + show]; +} + + +- (void)updateActionButton { + self.navigationItem.rightBarButtonItem.enabled = + [emailField.text length] > 0 && + [passwordField.text length] > 0 && + !loadingView; +} + + +- (DBRestClient*)restClient { + if (restClient == nil) { + restClient = [[DBRestClient alloc] initWithSession:[DBSession sharedSession]]; + restClient.delegate = self; + } + return restClient; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBMetadata.h b/Classes/ThirdParty/DropboxSDK/DBMetadata.h new file mode 100644 index 0000000..032fc46 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBMetadata.h @@ -0,0 +1,40 @@ +// +// DBMetadata.h +// DropboxSDK +// +// Created by Brian Smith on 5/3/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + + +@interface DBMetadata : NSObject { + BOOL thumbnailExists; + long long totalBytes; + NSDate* lastModifiedDate; + NSString* path; + BOOL isDirectory; + NSArray* contents; + NSString* hash; + NSString* humanReadableSize; + NSString* root; + NSString* icon; + long long revision; + BOOL isDeleted; +} + +- (id)initWithDictionary:(NSDictionary*)dict; + +@property (nonatomic, readonly) BOOL thumbnailExists; +@property (nonatomic, readonly) long long totalBytes; +@property (nonatomic, readonly) NSDate* lastModifiedDate; +@property (nonatomic, readonly) NSString* path; +@property (nonatomic, readonly) BOOL isDirectory; +@property (nonatomic, readonly) NSArray* contents; +@property (nonatomic, readonly) NSString* hash; +@property (nonatomic, readonly) NSString* humanReadableSize; +@property (nonatomic, readonly) NSString* root; +@property (nonatomic, readonly) NSString* icon; +@property (nonatomic, readonly) long long revision; +@property (nonatomic, readonly) BOOL isDeleted; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBMetadata.m b/Classes/ThirdParty/DropboxSDK/DBMetadata.m new file mode 100644 index 0000000..195c61d --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBMetadata.m @@ -0,0 +1,126 @@ +// +// DBMetadata.m +// DropboxSDK +// +// Created by Brian Smith on 5/3/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "DBMetadata.h" + +@implementation DBMetadata + ++ (NSDateFormatter*)dateFormatter { + NSMutableDictionary* dictionary = [[NSThread currentThread] threadDictionary]; + static NSString* dateFormatterKey = @"DBMetadataDateFormatter"; + + NSDateFormatter* dateFormatter = [dictionary objectForKey:dateFormatterKey]; + if (dateFormatter == nil) { + dateFormatter = [[NSDateFormatter new] autorelease]; + // Must set locale to ensure consistent parsing: + // http://developer.apple.com/iphone/library/qa/qa2010/qa1480.html + dateFormatter.locale = + [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]; + dateFormatter.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss Z"; + [dictionary setObject:dateFormatter forKey:dateFormatterKey]; + } + return dateFormatter; +} + +- (id)initWithDictionary:(NSDictionary*)dict { + if ((self = [super init])) { + thumbnailExists = [[dict objectForKey:@"thumb_exists"] boolValue]; + totalBytes = [[dict objectForKey:@"bytes"] longLongValue]; + + if ([dict objectForKey:@"modified"]) { + lastModifiedDate = + [[[DBMetadata dateFormatter] dateFromString:[dict objectForKey:@"modified"]] retain]; + } + + path = [[dict objectForKey:@"path"] retain]; + isDirectory = [[dict objectForKey:@"is_dir"] boolValue]; + + if ([dict objectForKey:@"contents"]) { + NSArray* subfileDicts = [dict objectForKey:@"contents"]; + NSMutableArray* mutableContents = + [[NSMutableArray alloc] initWithCapacity:[subfileDicts count]]; + for (NSDictionary* subfileDict in subfileDicts) { + DBMetadata* subfile = [[DBMetadata alloc] initWithDictionary:subfileDict]; + [mutableContents addObject:subfile]; + [subfile release]; + } + contents = mutableContents; + } + + hash = [[dict objectForKey:@"hash"] retain]; + humanReadableSize = [[dict objectForKey:@"size"] retain]; + root = [[dict objectForKey:@"root"] retain]; + icon = [[dict objectForKey:@"icon"] retain]; + revision = [[dict objectForKey:@"revision"] longLongValue]; + isDeleted = [[dict objectForKey:@"is_deleted"] boolValue]; + } + return self; +} + +- (void)dealloc { + [lastModifiedDate release]; + [path release]; + [contents release]; + [hash release]; + [humanReadableSize release]; + [root release]; + [icon release]; + [super dealloc]; +} + +@synthesize thumbnailExists; +@synthesize totalBytes; +@synthesize lastModifiedDate; +@synthesize path; +@synthesize isDirectory; +@synthesize contents; +@synthesize hash; +@synthesize humanReadableSize; +@synthesize root; +@synthesize icon; +@synthesize revision; +@synthesize isDeleted; + + +#pragma mark NSCoding methods + +- (id)initWithCoder:(NSCoder*)coder { + if ((self = [super init])) { + thumbnailExists = [coder decodeBoolForKey:@"thumbnailExists"]; + totalBytes = [coder decodeInt64ForKey:@"totalBytes"]; + lastModifiedDate = [[coder decodeObjectForKey:@"lastModifiedDate"] retain]; + path = [[coder decodeObjectForKey:@"path"] retain]; + isDirectory = [coder decodeBoolForKey:@"isDirectory"]; + contents = [[coder decodeObjectForKey:@"contents"] retain]; + hash = [[coder decodeObjectForKey:@"hash"] retain]; + humanReadableSize = [[coder decodeObjectForKey:@"humanReadableSize"] retain]; + root = [[coder decodeObjectForKey:@"root"] retain]; + icon = [[coder decodeObjectForKey:@"icon"] retain]; + revision = [coder decodeInt64ForKey:@"revision"]; + isDeleted = [coder decodeBoolForKey:@"isDeleted"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder*)coder { + [coder encodeBool:thumbnailExists forKey:@"thumbnailExists"]; + [coder encodeInt64:totalBytes forKey:@"totalBytes"]; + [coder encodeObject:lastModifiedDate forKey:@"lastModifiedDate"]; + [coder encodeObject:path forKey:@"path"]; + [coder encodeBool:isDirectory forKey:@"isDirectory"]; + [coder encodeObject:contents forKey:@"contents"]; + [coder encodeObject:hash forKey:@"hash"]; + [coder encodeObject:humanReadableSize forKey:@"humanReadableSize"]; + [coder encodeObject:root forKey:@"root"]; + [coder encodeObject:icon forKey:@"icon"]; + [coder encodeInt64:revision forKey:@"revision"]; + [coder encodeBool:isDeleted forKey:@"isDeleted"]; +} + + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBQuota.h b/Classes/ThirdParty/DropboxSDK/DBQuota.h new file mode 100644 index 0000000..b5cef72 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBQuota.h @@ -0,0 +1,23 @@ +// +// DBQuota.h +// DropboxSDK +// +// Created by Brian Smith on 5/3/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + + +@interface DBQuota : NSObject { + long long normalConsumedBytes; + long long sharedConsumedBytes; + long long totalBytes; +} + +- (id)initWithDictionary:(NSDictionary*)dict; + +@property (nonatomic, readonly) long long normalConsumedBytes; +@property (nonatomic, readonly) long long sharedConsumedBytes; +@property (nonatomic, readonly) long long totalConsumedBytes; +@property (nonatomic, readonly) long long totalBytes; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBQuota.m b/Classes/ThirdParty/DropboxSDK/DBQuota.m new file mode 100644 index 0000000..71ab624 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBQuota.m @@ -0,0 +1,52 @@ +// +// DBQuotaInfo.m +// DropboxSDK +// +// Created by Brian Smith on 5/3/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "DBQuota.h" + + +@implementation DBQuota + +- (id)initWithDictionary:(NSDictionary*)dict { + if ((self = [super init])) { + normalConsumedBytes = [[dict objectForKey:@"normal"] longLongValue]; + sharedConsumedBytes = [[dict objectForKey:@"shared"] longLongValue]; + totalBytes = [[dict objectForKey:@"quota"] longLongValue]; + } + return self; +} + +- (void)dealloc { + [super dealloc]; +} + +@synthesize normalConsumedBytes; +@synthesize sharedConsumedBytes; +@synthesize totalBytes; + +- (long long)totalConsumedBytes { + return normalConsumedBytes + sharedConsumedBytes; +} + + +#pragma mark NSCoding methods + +- (void)encodeWithCoder:(NSCoder*)coder { + [coder encodeInt64:normalConsumedBytes forKey:@"normalConsumedBytes"]; + [coder encodeInt64:sharedConsumedBytes forKey:@"sharedConsumedBytes"]; + [coder encodeInt64:totalBytes forKey:@"totalBytes"]; +} + +- (id)initWithCoder:(NSCoder*)coder { + self = [super init]; + normalConsumedBytes = [coder decodeInt64ForKey:@"normalConsumedBytes"]; + sharedConsumedBytes = [coder decodeInt64ForKey:@"sharedConsumedBytes"]; + totalBytes = [coder decodeInt64ForKey:@"totalBytes"]; + return self; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBRequest.h b/Classes/ThirdParty/DropboxSDK/DBRequest.h new file mode 100644 index 0000000..f051874 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBRequest.h @@ -0,0 +1,71 @@ +// +// DBRestRequest.h +// DropboxSDK +// +// Created by Brian Smith on 4/9/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + + +@protocol DBNetworkRequestDelegate; + +/* DBRestRequest will download a URL either into a file that you provied the name to or it will + create an NSData object with the result. When it has completed downloading the URL, it will + notify the target with a selector that takes the DBRestRequest as the only parameter. */ +@interface DBRequest : NSObject { + NSURLRequest* request; + id target; + SEL selector; + NSURLConnection* urlConnection; + NSFileHandle* fileHandle; + + SEL failureSelector; + SEL downloadProgressSelector; + SEL uploadProgressSelector; + NSString* resultFilename; + NSString* tempFilename; + NSDictionary* userInfo; + + NSHTTPURLResponse* response; + NSInteger bytesDownloaded; + CGFloat downloadProgress; + CGFloat uploadProgress; + NSMutableData* resultData; + NSError* error; +} + +/* Set this to get called when _any_ request starts or stops. This should hook into whatever + network activity indicator system you have. */ ++ (void)setNetworkRequestDelegate:(id)delegate; + +/* This constructor downloads the URL into the resultData object */ +- (id)initWithURLRequest:(NSURLRequest*)request andInformTarget:(id)target selector:(SEL)selector; + +/* Cancels the request and prevents it from sending additional messages to the delegate. */ +- (void)cancel; + +@property (nonatomic, assign) SEL failureSelector; // To send failure events to a different selector set this +@property (nonatomic, assign) SEL downloadProgressSelector; // To receive download progress events set this +@property (nonatomic, assign) SEL uploadProgressSelector; // To receive upload progress events set this +@property (nonatomic, retain) NSString* resultFilename; // The file to put the HTTP body in, otherwise body is stored in resultData +@property (nonatomic, retain) NSDictionary* userInfo; + +@property (nonatomic, readonly) NSURLRequest* request; +@property (nonatomic, readonly) NSHTTPURLResponse* response; +@property (nonatomic, readonly) NSInteger statusCode; +@property (nonatomic, readonly) CGFloat downloadProgress; +@property (nonatomic, readonly) CGFloat uploadProgress; +@property (nonatomic, readonly) NSData* resultData; +@property (nonatomic, readonly) NSString* resultString; +@property (nonatomic, readonly) NSObject* resultJSON; +@property (nonatomic, readonly) NSError* error; + +@end + + +@protocol DBNetworkRequestDelegate + +- (void)networkRequestStarted; +- (void)networkRequestStopped; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBRequest.m b/Classes/ThirdParty/DropboxSDK/DBRequest.m new file mode 100644 index 0000000..491d9c3 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBRequest.m @@ -0,0 +1,228 @@ +// +// DBRestRequest.m +// DropboxSDK +// +// Created by Brian Smith on 4/9/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "DBRequest.h" +#import "DBError.h" +#import "JSON.h" + + +static id networkRequestDelegate = nil; + +@implementation DBRequest + ++ (void)setNetworkRequestDelegate:(id)delegate { + networkRequestDelegate = delegate; +} + +- (id)initWithURLRequest:(NSURLRequest*)aRequest andInformTarget:(id)aTarget selector:(SEL)aSelector { + if ((self = [super init])) { + request = [aRequest retain]; + target = aTarget; + selector = aSelector; + + urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; + [networkRequestDelegate networkRequestStarted]; + } + return self; +} + +- (void) dealloc { + [urlConnection cancel]; + + [request release]; + [urlConnection release]; + [fileHandle release]; + [userInfo release]; + [response release]; + [resultFilename release]; + [tempFilename release]; + [resultData release]; + [error release]; + [super dealloc]; +} + +@synthesize failureSelector; +@synthesize downloadProgressSelector; +@synthesize uploadProgressSelector; +@synthesize userInfo; +@synthesize request; +@synthesize response; +@synthesize downloadProgress; +@synthesize uploadProgress; +@synthesize resultData; +@synthesize resultFilename; +@synthesize error; + +- (NSString*)resultString { + return [[[NSString alloc] + initWithData:resultData encoding:NSUTF8StringEncoding] + autorelease]; +} + +- (NSObject*)resultJSON { + return [[self resultString] JSONValue]; +} + +- (NSInteger)statusCode { + return [response statusCode]; +} + +- (void)cancel { + [urlConnection cancel]; + target = nil; + + if (tempFilename) { + [fileHandle closeFile]; + NSError* rmError; + if (![[NSFileManager defaultManager] removeItemAtPath:tempFilename error:&rmError]) { + NSLog(@"DBRequest#cancel Error removing temp file: %@", rmError); + } + } + + [networkRequestDelegate networkRequestStopped]; +} + +#pragma mark NSURLConnection delegate methods + +- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)aResponse { + response = [(NSHTTPURLResponse*)aResponse retain]; + + if (resultFilename && [self statusCode] == 200) { + // Create the file here so it's created in case it's zero length + // File is downloaded into a temporary file and then moved over when completed successfully + NSString* filename = + [NSString stringWithFormat:@"%.0f", 1000*[NSDate timeIntervalSinceReferenceDate]]; + tempFilename = [[NSTemporaryDirectory() stringByAppendingPathComponent:filename] retain]; + + NSFileManager* fileManager = [[NSFileManager new] autorelease]; + BOOL success = [fileManager createFileAtPath:tempFilename contents:nil attributes:nil]; + if (!success) { + NSLog(@"DBRequest#connection:didReceiveData: Error creating file at path: %@", + tempFilename); + } + + fileHandle = [[NSFileHandle fileHandleForWritingAtPath:tempFilename] retain]; + } +} + +- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data { + if (resultFilename && [self statusCode] == 200) { + @try { + [fileHandle writeData:data]; + } @catch (NSException* e) { + // In case we run out of disk space + [urlConnection cancel]; + [fileHandle closeFile]; + [[NSFileManager defaultManager] removeItemAtPath:tempFilename error:nil]; + error = [[NSError alloc] initWithDomain:DBErrorDomain + code:DBErrorInsufficientDiskSpace userInfo:userInfo]; + + SEL sel = failureSelector ? failureSelector : selector; + [target performSelector:sel withObject:self]; + + [networkRequestDelegate networkRequestStopped]; + + return; + } + } else { + if (resultData == nil) { + resultData = [NSMutableData new]; + } + [resultData appendData:data]; + } + + bytesDownloaded += [data length]; + NSInteger contentLength = [[[response allHeaderFields] objectForKey:@"Content-Length"] intValue]; + downloadProgress = (CGFloat)bytesDownloaded / (CGFloat)contentLength; + if (downloadProgressSelector) { + [target performSelector:downloadProgressSelector withObject:self]; + } +} + +- (void)connectionDidFinishLoading:(NSURLConnection*)connection { + [fileHandle closeFile]; + [fileHandle release]; + fileHandle = nil; + + if (self.statusCode != 200) { + NSMutableDictionary* errorUserInfo = [NSMutableDictionary dictionaryWithDictionary:userInfo]; + // To get error userInfo, first try and make sense of the response as JSON, if that + // fails then send back the string as an error message + NSString* resultString = [self resultString]; + if ([resultString length] > 0) { + @try { + SBJsonParser *jsonParser = [SBJsonParser new]; + NSObject* resultJSON = [jsonParser objectWithString:resultString]; + [jsonParser release]; + + if ([resultJSON isKindOfClass:[NSDictionary class]]) { + [errorUserInfo addEntriesFromDictionary:(NSDictionary*)resultJSON]; + } + } @catch (NSException* e) { + [errorUserInfo setObject:resultString forKey:@"errorMessage"]; + } + } + error = [[NSError alloc] initWithDomain:@"dropbox.com" code:self.statusCode userInfo:errorUserInfo]; + } else if (tempFilename) { + // Move temp file over to desired file + NSFileManager* fileManager = [[NSFileManager new] autorelease]; + [fileManager removeItemAtPath:resultFilename error:nil]; + NSError* moveError; + BOOL success = [fileManager moveItemAtPath:tempFilename toPath:resultFilename error:&moveError]; + if (!success) { + NSLog(@"DBRequest#connectionDidFinishLoading: error moving temp file to desired location: %@", + [moveError localizedDescription]); + error = [[NSError alloc] initWithDomain:moveError.domain code:moveError.code userInfo:self.userInfo]; + } + + [tempFilename release]; + tempFilename = nil; + } + + SEL sel = (error && failureSelector) ? failureSelector : selector; + [target performSelector:sel withObject:self]; + + [networkRequestDelegate networkRequestStopped]; +} + +- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)anError { + [fileHandle closeFile]; + error = [[NSError alloc] initWithDomain:anError.domain code:anError.code userInfo:self.userInfo]; + bytesDownloaded = 0; + downloadProgress = 0; + uploadProgress = 0; + + if (tempFilename) { + NSFileManager* fileManager = [[NSFileManager new] autorelease]; + NSError* removeError; + BOOL success = [fileManager removeItemAtPath:tempFilename error:&removeError]; + if (!success) { + NSLog(@"DBRequest#connection:didFailWithError: error removing temporary file: %@", + [removeError localizedDescription]); + } + [tempFilename release]; + tempFilename = nil; + } + + SEL sel = failureSelector ? failureSelector : selector; + [target performSelector:sel withObject:self]; + + [networkRequestDelegate networkRequestStopped]; +} + +- (void)connection:(NSURLConnection*)connection didSendBodyData:(NSInteger)bytesWritten + totalBytesWritten:(NSInteger)totalBytesWritten + totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { + + uploadProgress = (CGFloat)totalBytesWritten / (CGFloat)totalBytesExpectedToWrite; + if (uploadProgressSelector) { + [target performSelector:uploadProgressSelector withObject:self]; + } +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBRestClient.h b/Classes/ThirdParty/DropboxSDK/DBRestClient.h new file mode 100644 index 0000000..465189f --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBRestClient.h @@ -0,0 +1,138 @@ +// +// DBRestClient.h +// DropboxSDK +// +// Created by Brian Smith on 4/9/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + + +#import "DBSession.h" + +@protocol DBRestClientDelegate; +@class DBAccountInfo; +@class DBMetadata; + +extern NSString* kDBProtocolHTTP; +extern NSString* kDBProtocolHTTPS; + +@interface DBRestClient : NSObject { + DBSession* session; + NSString* root; + NSMutableSet* requests; + /* Map from path to the load request. Needs to be expanded to a general framework for cancelling + requests. */ + NSMutableDictionary* loadRequests; + id delegate; +} + +- (id)initWithSession:(DBSession*)session; + +/* New developers should not use this method, and instead just use initWithSession: */ +- (id)initWithSession:(DBSession *)session root:(NSString*)root; + +/* Logs in as the user with the given email/password and stores the OAuth tokens on the session + object */ +- (void)loginWithEmail:(NSString*)email password:(NSString*)password; + +/* Loads metadata for the object at the given root/path and returns the result to the delegate as a + dictionary */ +- (void)loadMetadata:(NSString*)path withHash:(NSString*)hash; +- (void)loadMetadata:(NSString*)path; + +/* Loads the file contents at the given root/path and stores the result into destinationPath */ +- (void)loadFile:(NSString *)path intoPath:(NSString *)destinationPath; +- (void)cancelFileLoad:(NSString*)path; + +- (void)loadThumbnail:(NSString *)path ofSize:(NSString *)size intoPath:(NSString *)destinationPath; + +/* Uploads a file that will be named filename to the given root/path on the server. It will upload + the contents of the file at sourcePath */ +- (void)uploadFile:(NSString*)filename toPath:(NSString*)path fromPath:(NSString *)sourcePath; + +/* Creates a folder at the given root/path */ +- (void)createFolder:(NSString*)path; + +- (void)deletePath:(NSString*)path; + +- (void)copyFrom:(NSString*)from_path toPath:(NSString *)to_path; + +- (void)moveFrom:(NSString*)from_path toPath:(NSString *)to_path; + +- (void)loadAccountInfo; + +- (void)createAccount:(NSString *)email password:(NSString *)password firstName:(NSString *)firstName + lastName:(NSString *)lastName; + +@property (nonatomic, assign) id delegate; + +@end + + + + +/* The delegate provides allows the user to get the result of the calls made on the DBRestClient. + Right now, the error parameter of failed calls may be nil and [error localizedDescription] does + not contain an error message appropriate to show to the user. */ +@protocol DBRestClientDelegate + +@optional + +- (void)restClientDidLogin:(DBRestClient*)client; +- (void)restClient:(DBRestClient*)client loginFailedWithError:(NSError*)error; + +- (void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata; +- (void)restClient:(DBRestClient*)client metadataUnchangedAtPath:(NSString*)path; +- (void)restClient:(DBRestClient*)client loadMetadataFailedWithError:(NSError*)error; +// [error userInfo] contains the root and path of the call that failed + +- (void)restClient:(DBRestClient*)client loadedAccountInfo:(DBAccountInfo*)info; +- (void)restClient:(DBRestClient*)client loadAccountInfoFailedWithError:(NSError*)error; + +- (void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath; +// Implement the following callback instead of the previous if you care about the value of the +// Content-Type HTTP header. Only one will be called per successful response. +- (void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath contentType:(NSString*)contentType; +- (void)restClient:(DBRestClient*)client loadProgress:(CGFloat)progress forFile:(NSString*)destPath; +- (void)restClient:(DBRestClient*)client loadFileFailedWithError:(NSError*)error; +// [error userInfo] contains the destinationPath + +- (void)restClient:(DBRestClient*)client loadedThumbnail:(NSString*)destPath; +- (void)restClient:(DBRestClient*)client loadThumbnailFailedWithError:(NSError*)error; + +- (void)restClient:(DBRestClient*)client uploadedFile:(NSString*)destPath from:(NSString*)srcPath; +- (void)restClient:(DBRestClient*)client uploadProgress:(CGFloat)progress +forFile:(NSString*)destPath from:(NSString*)srcPath; +- (void)restClient:(DBRestClient*)client uploadFileFailedWithError:(NSError*)error; +// [error userInfo] contains the sourcePath + +// Deprecated upload callbacks +- (void)restClient:(DBRestClient*)client uploadedFile:(NSString*)srcPath; +- (void)restClient:(DBRestClient*)client uploadProgress:(CGFloat)progress forFile:(NSString*)srcPath; + +- (void)restClient:(DBRestClient*)client createdFolder:(DBMetadata*)folder; +// Folder is the metadata for the newly created folder +- (void)restClient:(DBRestClient*)client createFolderFailedWithError:(NSError*)error; +// [error userInfo] contains the root and path + +- (void)restClient:(DBRestClient*)client deletedPath:(NSString *)path; +// Folder is the metadata for the newly created folder +- (void)restClient:(DBRestClient*)client deletePathFailedWithError:(NSError*)error; +// [error userInfo] contains the root and path + +- (void)restClient:(DBRestClient*)client copiedPath:(NSString *)from_path toPath:(NSString *)to_path; +// Folder is the metadata for the newly created folder +- (void)restClient:(DBRestClient*)client copyPathFailedWithError:(NSError*)error; +// [error userInfo] contains the root and path +// +- (void)restClient:(DBRestClient*)client movedPath:(NSString *)from_path toPath:(NSString *)to_path; +// Folder is the metadata for the newly created folder +- (void)restClient:(DBRestClient*)client movePathFailedWithError:(NSError*)error; +// [error userInfo] contains the root and path + +- (void)restClientCreatedAccount:(DBRestClient*)client; +- (void)restClient:(DBRestClient*)client createAccountFailedWithError:(NSError *)error; + +@end + + diff --git a/Classes/ThirdParty/DropboxSDK/DBRestClient.m b/Classes/ThirdParty/DropboxSDK/DBRestClient.m new file mode 100644 index 0000000..6535beb --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBRestClient.m @@ -0,0 +1,800 @@ +// +// DBRestClient.m +// DropboxSDK +// +// Created by Brian Smith on 4/9/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "DBRestClient.h" +#import "DBAccountInfo.h" +#import "DBError.h" +#import "DBMetadata.h" +#import "DBRequest.h" +#import "MPOAuthURLRequest.h" +#import "MPURLRequestParameter.h" +#import "MPOAuthSignatureParameter.h" +#import "NSString+URLEscapingAdditions.h" + + +NSString* kDBProtocolHTTP = @"http"; +NSString* kDBProtocolHTTPS = @"https"; + + +@interface DBRestClient () + +// This method escapes all URI escape characters except / ++ (NSString*)escapePath:(NSString*)path; + +- (NSMutableURLRequest*)requestWithProtocol:(NSString*)protocol host:(NSString*)host path:(NSString*)path + parameters:(NSDictionary*)params; + +- (NSMutableURLRequest*)requestWithProtocol:(NSString*)protocol host:(NSString*)host path:(NSString*)path + parameters:(NSDictionary*)params method:(NSString*)method; + +- (void)checkForAuthenticationFailure:(DBRequest*)request; + +@end + + +@implementation DBRestClient + +- (id)initWithSession:(DBSession*)aSession { + return [self initWithSession:aSession root:@"dropbox"]; +} + +- (id)initWithSession:(DBSession*)aSession root:(NSString*)aRoot { + if ((self = [super init])) { + session = [aSession retain]; + root = [aRoot retain]; + requests = [[NSMutableSet alloc] init]; + loadRequests = [[NSMutableDictionary alloc] init]; + } + return self; +} + + +- (void)dealloc { + for (DBRequest* request in requests) { + [request cancel]; + } + [requests release]; + for (DBRequest* request in [loadRequests allValues]) { + [request cancel]; + } + [loadRequests release]; + [session release]; + [root release]; + [super dealloc]; +} + + + +@synthesize delegate; + + +- (void)loginWithEmail:(NSString*)email password:(NSString*)password { + NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys: + email, @"email", + password, @"password", nil]; + + NSURLRequest* urlRequest = [self requestWithProtocol:kDBProtocolHTTPS host:kDBDropboxAPIHost + path:@"/token" parameters:params]; + + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidLogin:)] + autorelease]; + + [requests addObject:request]; +} + + + +- (void)requestDidLogin:(DBRequest*)request { + if (request.error) { + if ([delegate respondsToSelector:@selector(restClient:loginFailedWithError:)]) { + [delegate restClient:self loginFailedWithError:request.error]; + } + } else { + NSDictionary* result = (NSDictionary*)request.resultJSON; + NSString* token = [result objectForKey:@"token"]; + NSString* secret = [result objectForKey:@"secret"]; + [session updateAccessToken:token accessTokenSecret:secret]; + if ([delegate respondsToSelector:@selector(restClientDidLogin:)]) { + [delegate restClientDidLogin:self]; + } + } + + [requests removeObject:request]; +} + + + +- (void)loadMetadata:(NSString*)path withHash:(NSString*)hash +{ + NSDictionary* params = nil; + if (hash) { + params = [NSDictionary dictionaryWithObject:hash forKey:@"hash"]; + } + + NSString* fullPath = [NSString stringWithFormat:@"/metadata/%@%@", root, path]; + NSURLRequest* urlRequest = + [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:fullPath parameters:params]; + + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidLoadMetadata:)] + autorelease]; + + request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:root, @"root", path, @"path", nil]; + + [requests addObject:request]; +} + +- (void)loadMetadata:(NSString*)path +{ + [self loadMetadata:path withHash:nil]; +} + + +- (void)requestDidLoadMetadata:(DBRequest*)request +{ + if (request.statusCode == 304) { + if ([delegate respondsToSelector:@selector(restClient:metadataUnchangedAtPath:)]) { + NSString* path = [request.userInfo objectForKey:@"path"]; + [delegate restClient:self metadataUnchangedAtPath:path]; + } + } else if (request.error) { + [self checkForAuthenticationFailure:request]; + if ([delegate respondsToSelector:@selector(restClient:loadMetadataFailedWithError:)]) { + [delegate restClient:self loadMetadataFailedWithError:request.error]; + } + } else { + [self performSelectorInBackground:@selector(parseMetadataWithRequest:) withObject:request]; + } + + [requests removeObject:request]; +} + + +- (void)parseMetadataWithRequest:(DBRequest*)request { + NSAutoreleasePool* pool = [NSAutoreleasePool new]; + + NSDictionary* result = (NSDictionary*)[request resultJSON]; + DBMetadata* metadata = [[[DBMetadata alloc] initWithDictionary:result] autorelease]; + [self performSelectorOnMainThread:@selector(didParseMetadata:) withObject:metadata waitUntilDone:NO]; + + [pool drain]; +} + + +- (void)didParseMetadata:(DBMetadata*)metadata { + if ([delegate respondsToSelector:@selector(restClient:loadedMetadata:)]) { + [delegate restClient:self loadedMetadata:metadata]; + } +} + + +- (void)loadFile:(NSString *)path intoPath:(NSString *)destinationPath +{ + NSString* fullPath = [NSString stringWithFormat:@"/files/%@%@", root, path]; + + NSURLRequest* urlRequest = + [self requestWithProtocol:kDBProtocolHTTPS host:kDBDropboxAPIContentHost path:fullPath parameters:nil]; + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidLoadFile:)] + autorelease]; + request.resultFilename = destinationPath; + request.downloadProgressSelector = @selector(requestLoadProgress:); + request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + root, @"root", + path, @"path", + destinationPath, @"destinationPath", nil]; + [loadRequests setObject:request forKey:path]; +} + + +- (void)cancelFileLoad:(NSString*)path { + DBRequest* outstandingRequest = [loadRequests objectForKey:path]; + if (outstandingRequest) { + [outstandingRequest cancel]; + [loadRequests removeObjectForKey:path]; + } +} + + +- (void)requestLoadProgress:(DBRequest*)request { + if ([delegate respondsToSelector:@selector(restClient:loadProgress:forFile:)]) { + [delegate restClient:self loadProgress:request.downloadProgress forFile:request.resultFilename]; + } +} + + +- (void)restClient:(DBRestClient*)restClient loadedFile:(NSString*)destPath +contentType:(NSString*)contentType eTag:(NSString*)eTag { + // Empty selector to get the signature from +} + +- (void)requestDidLoadFile:(DBRequest*)request { + NSString* path = [request.userInfo objectForKey:@"path"]; + + if (request.error) { + [self checkForAuthenticationFailure:request]; + if ([delegate respondsToSelector:@selector(restClient:loadFileFailedWithError:)]) { + [delegate restClient:self loadFileFailedWithError:request.error]; + } + } else { + NSString* filename = request.resultFilename; + NSDictionary* headers = [request.response allHeaderFields]; + NSString* contentType = [headers objectForKey:@"Content-Type"]; + NSString* eTag = [headers objectForKey:@"Etag"]; + if ([delegate respondsToSelector:@selector(restClient:loadedFile:)]) { + [delegate restClient:self loadedFile:filename]; + } else if ([delegate respondsToSelector:@selector(restClient:loadedFile:contentType:)]) { + [delegate restClient:self loadedFile:filename contentType:contentType]; + } else if ([delegate respondsToSelector:@selector(restClient:loadedFile:contentType:eTag:)]) { + // This code is for the official Dropbox client to get eTag information from the server + NSMethodSignature* signature = + [self methodSignatureForSelector:@selector(restClient:loadedFile:contentType:eTag:)]; + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setTarget:delegate]; + [invocation setSelector:@selector(restClient:loadedFile:contentType:eTag:)]; + [invocation setArgument:&self atIndex:2]; + [invocation setArgument:&filename atIndex:3]; + [invocation setArgument:&contentType atIndex:4]; + [invocation setArgument:&eTag atIndex:5]; + [invocation invoke]; + } + } + + [loadRequests removeObjectForKey:path]; +} + + + +- (void)loadThumbnail:(NSString *)path ofSize:(NSString *)size intoPath:(NSString *)destinationPath +{ + NSString* fullPath = [NSString stringWithFormat:@"/thumbnails/%@%@", root, path]; + NSDictionary *params = nil; + + if(size) { + params = [NSDictionary dictionaryWithObjectsAndKeys: size, @"size", nil]; + } + + NSURLRequest* urlRequest = + [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIContentHost path:fullPath parameters:params]; + + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidLoadThumbnail:)] + autorelease]; + + request.resultFilename = destinationPath; + request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + root, @"root", + path, @"path", + destinationPath, @"destinationPath", nil]; + [requests addObject:request]; +} + + + +- (void)requestDidLoadThumbnail:(DBRequest*)request +{ + if (request.error) { + [self checkForAuthenticationFailure:request]; + if ([delegate respondsToSelector:@selector(restClient:loadThumbnailFailedWithError:)]) { + [delegate restClient:self loadThumbnailFailedWithError:request.error]; + } + } else { + if ([delegate respondsToSelector:@selector(restClient:loadedThumbnail:)]) { + [delegate restClient:self loadedThumbnail:request.resultFilename]; + } + } + + [requests removeObject:request]; +} + + + + +NSString *createFakeSignature(DBSession *session, NSArray *params, NSString *filename, NSURL *baseUrl) +{ + NSArray* extraParams = [MPURLRequestParameter parametersFromDictionary: + [NSDictionary dictionaryWithObject:filename forKey:@"file"]]; + + NSMutableArray* paramList = [NSMutableArray arrayWithArray:params]; + [paramList addObjectsFromArray:extraParams]; + [paramList sortUsingSelector:@selector(compare:)]; + NSString* paramString = [MPURLRequestParameter parameterStringForParameters:paramList]; + + MPOAuthURLRequest* oauthRequest = + [[[MPOAuthURLRequest alloc] initWithURL:baseUrl andParameters:paramList] autorelease]; + oauthRequest.HTTPMethod = @"POST"; + MPOAuthSignatureParameter *signatureParameter = + [[[MPOAuthSignatureParameter alloc] + initWithText:paramString andSecret:session.credentialStore.signingKey + forRequest:oauthRequest usingMethod:session.credentialStore.signatureMethod] + autorelease]; + + return [signatureParameter URLEncodedParameterString]; +} + +NSMutableURLRequest *createRealRequest(DBSession *session, NSArray *params, NSString *urlString, NSString *signatureText) +{ + NSMutableArray *paramList = [NSMutableArray arrayWithArray:params]; + // Then rebuild request using that signature + [paramList sortUsingSelector:@selector(compare:)]; + NSMutableString* realParamString = [[[NSMutableString alloc] initWithString: + [MPURLRequestParameter parameterStringForParameters:paramList]] + autorelease]; + [realParamString appendFormat:@"&%@", signatureText]; + + NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", urlString, realParamString]]; + NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url]; + urlRequest.HTTPMethod = @"POST"; + + return urlRequest; +} + +// Returns DBErrorNone if no errors were encountered +DBErrorCode addFileUploadToRequest(NSMutableURLRequest *urlRequest, NSString *filename, NSString *sourcePath) +{ + // Create input stream + CFUUIDRef uuid = CFUUIDCreate(NULL); + NSString* stringBoundary = [(NSString*)CFUUIDCreateString(NULL, uuid) autorelease]; + CFRelease(uuid); + + NSString* contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",stringBoundary]; + [urlRequest addValue:contentType forHTTPHeaderField: @"Content-Type"]; + + NSString* tempFilename = + [NSString stringWithFormat: @"%.0f.txt", [NSDate timeIntervalSinceReferenceDate] * 1000.0]; + NSString *tempFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:tempFilename]; + + //setting up the body + NSMutableData* bodyData = [NSMutableData data]; + [bodyData appendData: + [[NSString stringWithFormat:@"--%@\r\n", stringBoundary] + dataUsingEncoding:NSUTF8StringEncoding]]; + + // Add data to upload + [bodyData appendData: + [[NSString stringWithFormat: + @"Content-Disposition: form-data; name=\"file\"; filename=\"%@\"\r\n", filename] + dataUsingEncoding:NSUTF8StringEncoding]]; + [bodyData appendData: + [[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"] + dataUsingEncoding:NSUTF8StringEncoding]]; + + if (![[NSFileManager defaultManager] createFileAtPath:tempFilePath contents:bodyData attributes:nil]) { + NSLog(@"DBRestClient#uploadFileToRoot:path:filename:fromPath: failed to create file"); + return DBErrorGenericError; + } + + NSFileHandle* bodyFile = [NSFileHandle fileHandleForWritingAtPath:tempFilePath]; + [bodyFile seekToEndOfFile]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) { + NSFileHandle* readFile = [NSFileHandle fileHandleForReadingAtPath:sourcePath]; + NSData* readData; + while ((readData = [readFile readDataOfLength:1024 * 512]) != nil && [readData length] > 0) { + @try { + [bodyFile writeData:readData]; + } @catch (NSException* e) { + NSLog(@"DBRestClient#uploadFileToRoot:path:filename:fromPath: failed to write data"); + [readFile closeFile]; + [bodyFile closeFile]; + [[NSFileManager defaultManager] removeItemAtPath:tempFilePath error:nil]; + return DBErrorInsufficientDiskSpace; + } + } + [readFile closeFile]; + } else { + NSLog(@"DBRestClient#uploadFileToRoot:path:filename:fromPath: unable to open sourceFile"); + } + + @try { + [bodyFile writeData: + [[NSString stringWithFormat:@"\r\n--%@--\r\n", stringBoundary] + dataUsingEncoding:NSUTF8StringEncoding]]; + } @catch (NSException* e) { + NSLog(@"DBRestClient#uploadFileToRoot:path:filename:fromPath: failed to write end of data"); + [bodyFile closeFile]; + [[NSFileManager defaultManager] removeItemAtPath:tempFilePath error:nil]; + return DBErrorInsufficientDiskSpace; + } + + NSString* contentLength = [NSString stringWithFormat: @"%qu", [bodyFile offsetInFile]]; + [urlRequest addValue:contentLength forHTTPHeaderField: @"Content-Length"]; + [bodyFile closeFile]; + + urlRequest.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:tempFilePath]; + + return DBErrorNone; +} + + + +- (void)uploadFile:(NSString*)filename toPath:(NSString*)path fromPath:(NSString *)sourcePath +{ + if (![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) { + NSDictionary* userInfo = [NSDictionary dictionaryWithObject:sourcePath forKey:@"sourcePath"]; + NSError* error = + [NSError errorWithDomain:DBErrorDomain code:DBErrorFileNotFound userInfo:userInfo]; + if ([delegate respondsToSelector:@selector(restClient:uploadFileFailedWithError:)]) { + [delegate restClient:self uploadFileFailedWithError:error]; + } + return; + } + + // path is the directory the file will be uploaded to, make sure it doesn't have a trailing / + // (unless it's the root dir) and is properly escaped + NSString* trimmedPath; + if ([path length] > 1 && [path characterAtIndex:[path length]-1] == '/') { + trimmedPath = [path substringToIndex:[path length]-1]; + } else { + trimmedPath = path; + } + NSString* escapedPath = [DBRestClient escapePath:trimmedPath]; + + NSString* urlString = [NSString stringWithFormat:@"%@://%@/%@/files/%@%@", + kDBProtocolHTTPS, kDBDropboxAPIContentHost, kDBDropboxAPIVersion, root, escapedPath]; + NSURL* baseUrl = [NSURL URLWithString:urlString]; + NSArray* params = [session.credentialStore oauthParameters]; + + NSString *escapedFilename = [filename stringByReplacingOccurrencesOfString:@";" withString:@"-"]; + + NSString *signatureText = createFakeSignature(session, params, escapedFilename, baseUrl); + + NSMutableURLRequest *urlRequest = createRealRequest(session, params, urlString, signatureText); + + DBErrorCode errorCode = addFileUploadToRequest(urlRequest, escapedFilename, sourcePath); + if(errorCode == DBErrorNone) { + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidUploadFile:)] + autorelease]; + request.uploadProgressSelector = @selector(requestUploadProgress:); + NSString* dropboxPath = [path stringByAppendingPathComponent:filename]; + request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + root, @"root", + path, @"path", + dropboxPath, @"destinationPath", + sourcePath, @"sourcePath", nil]; + [requests addObject:request]; + } else { + NSDictionary* userInfo = [NSDictionary dictionaryWithObject:sourcePath forKey:@"sourcePath"]; + NSError* error = + [NSError errorWithDomain:DBErrorDomain code:errorCode userInfo:userInfo]; + if ([delegate respondsToSelector:@selector(restClient:uploadFileFailedWithError:)]) { + [delegate restClient:self uploadFileFailedWithError:error]; + } + } +} + + +- (void)requestUploadProgress:(DBRequest*)request { + NSString* sourcePath = [(NSDictionary*)request.userInfo objectForKey:@"sourcePath"]; + NSString* destPath = [request.userInfo objectForKey:@"destinationPath"]; + + if ([delegate respondsToSelector:@selector(restClient:uploadProgress:forFile:from:)]) { + [delegate restClient:self uploadProgress:request.uploadProgress + forFile:destPath from:sourcePath]; + } else if ([delegate respondsToSelector:@selector(restClient:uploadProgress:forFile:)]) { + [delegate restClient:self uploadProgress:request.uploadProgress forFile:sourcePath]; + } +} + + +- (void)requestDidUploadFile:(DBRequest*)request { + if (request.error) { + [self checkForAuthenticationFailure:request]; + if ([delegate respondsToSelector:@selector(restClient:uploadFileFailedWithError:)]) { + [delegate restClient:self uploadFileFailedWithError:request.error]; + } + } else { + NSString* sourcePath = [(NSDictionary*)request.userInfo objectForKey:@"sourcePath"]; + NSString* destPath = [request.userInfo objectForKey:@"destinationPath"]; + if ([delegate respondsToSelector:@selector(restClient:uploadedFile:from:)]) { + [delegate restClient:self uploadedFile:destPath from:sourcePath]; + } else if ([delegate respondsToSelector:@selector(restClient:uploadedFile:)]) { + [delegate restClient:self uploadedFile:sourcePath]; + } + } + + [requests removeObject:request]; +} + + + +- (void)moveFrom:(NSString*)from_path toPath:(NSString *)to_path +{ + NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys: + root, @"root", + from_path, @"from_path", + to_path, @"to_path", nil]; + + NSMutableURLRequest* urlRequest = + [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:@"/fileops/move" + parameters:params method:@"POST"]; + + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidMovePath:)] + autorelease]; + + request.userInfo = params; + [requests addObject:request]; +} + + + +- (void)requestDidMovePath:(DBRequest*)request { + if (request.error) { + [self checkForAuthenticationFailure:request]; + if ([delegate respondsToSelector:@selector(restClient:movePathFailedWithError:)]) { + [delegate restClient:self movePathFailedWithError:request.error]; + } + } else { + NSDictionary *params = (NSDictionary *)request.userInfo; + + if ([delegate respondsToSelector:@selector(restClient:movedPath:toPath:)]) { + [delegate restClient:self movedPath:[params valueForKey:@"from_path"] + toPath:[params valueForKey:@"to_path"]]; + } + } + + [requests removeObject:request]; +} + + +- (void)copyFrom:(NSString*)from_path toPath:(NSString *)to_path +{ + NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys: + root, @"root", + from_path, @"from_path", + to_path, @"to_path", nil]; + + NSMutableURLRequest* urlRequest = + [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:@"/fileops/copy" + parameters:params method:@"POST"]; + + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidCopyPath:)] + autorelease]; + + request.userInfo = params; + [requests addObject:request]; +} + + + +- (void)requestDidCopyPath:(DBRequest*)request { + if (request.error) { + [self checkForAuthenticationFailure:request]; + if ([delegate respondsToSelector:@selector(restClient:copyPathFailedWithError:)]) { + [delegate restClient:self copyPathFailedWithError:request.error]; + } + } else { + NSDictionary *params = (NSDictionary *)request.userInfo; + + if ([delegate respondsToSelector:@selector(restClient:copiedPath:toPath:)]) { + [delegate restClient:self copiedPath:[params valueForKey:@"from_path"] + toPath:[params valueForKey:@"to_path"]]; + } + } + + [requests removeObject:request]; +} + + +- (void)deletePath:(NSString*)path +{ + NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys: + root, @"root", + path, @"path", nil]; + + NSMutableURLRequest* urlRequest = + [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:@"/fileops/delete" + parameters:params method:@"POST"]; + + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidDeletePath:)] + autorelease]; + + request.userInfo = params; + [requests addObject:request]; +} + + + +- (void)requestDidDeletePath:(DBRequest*)request { + if (request.error) { + [self checkForAuthenticationFailure:request]; + if ([delegate respondsToSelector:@selector(restClient:deletePathFailedWithError:)]) { + [delegate restClient:self deletePathFailedWithError:request.error]; + } + } else { + if ([delegate respondsToSelector:@selector(restClient:deletedPath:)]) { + NSString* path = [request.userInfo objectForKey:@"path"]; + [delegate restClient:self deletedPath:path]; + } + } + + [requests removeObject:request]; +} + + + + +- (void)createFolder:(NSString*)path +{ + NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys: + root, @"root", + path, @"path", nil]; + + NSString* fullPath = @"/fileops/create_folder"; + NSMutableURLRequest* urlRequest = + [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:fullPath + parameters:params method:@"POST"]; + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidCreateDirectory:)] + autorelease]; + request.userInfo = params; + [requests addObject:request]; +} + + + +- (void)requestDidCreateDirectory:(DBRequest*)request { + if (request.error) { + [self checkForAuthenticationFailure:request]; + if ([delegate respondsToSelector:@selector(restClient:createFolderFailedWithError:)]) { + [delegate restClient:self createFolderFailedWithError:request.error]; + } + } else { + NSDictionary* result = (NSDictionary*)[request resultJSON]; + DBMetadata* metadata = [[[DBMetadata alloc] initWithDictionary:result] autorelease]; + if ([delegate respondsToSelector:@selector(restClient:createdFolder:)]) { + [delegate restClient:self createdFolder:metadata]; + } + } + + [requests removeObject:request]; +} + + + +- (void)loadAccountInfo +{ + NSURLRequest* urlRequest = + [self requestWithProtocol:kDBProtocolHTTP host:kDBDropboxAPIHost path:@"/account/info" parameters:nil]; + + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidLoadAccountInfo:)] + autorelease]; + + request.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:root, @"root", nil]; + + [requests addObject:request]; +} + + +- (void)requestDidLoadAccountInfo:(DBRequest*)request +{ + if (request.error) { + [self checkForAuthenticationFailure:request]; + if ([delegate respondsToSelector:@selector(restClient:loadAccountInfoFailedWithError:)]) { + [delegate restClient:self loadAccountInfoFailedWithError:request.error]; + } + } else { + NSDictionary* result = (NSDictionary*)[request resultJSON]; + DBAccountInfo* accountInfo = [[[DBAccountInfo alloc] initWithDictionary:result] autorelease]; + if ([delegate respondsToSelector:@selector(restClient:loadedAccountInfo:)]) { + [delegate restClient:self loadedAccountInfo:accountInfo]; + } + } + + [requests removeObject:request]; +} + +- (void)createAccount:(NSString *)email password:(NSString *)password firstName:(NSString *)firstName lastName:(NSString *)lastName +{ + NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys: + email, @"email", + password, @"password", + firstName, @"first_name", + lastName, @"last_name", nil]; + + NSString* fullPath = @"/account"; + NSMutableURLRequest* urlRequest = + [self requestWithProtocol:kDBProtocolHTTPS host:kDBDropboxAPIHost path:fullPath + parameters:params method:@"POST"]; + + DBRequest* request = + [[[DBRequest alloc] + initWithURLRequest:urlRequest andInformTarget:self selector:@selector(requestDidCreateAccount:)] + autorelease]; + + request.userInfo = params; + + [requests addObject:request]; +} + +- (void)requestDidCreateAccount:(DBRequest *)request +{ + if(request.error) { + if([delegate respondsToSelector:@selector(restClient:createAccountFailedWithError:)]) { + [delegate restClient:self createAccountFailedWithError:request.error]; + } + } else { + if ([delegate respondsToSelector:@selector(restClientCreatedAccount:)]) { + [delegate restClientCreatedAccount:self]; + } + } + + [requests removeObject:request]; +} + + +#pragma mark private methods + ++ (NSString*)escapePath:(NSString*)path { + CFStringEncoding encoding = CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding); + NSString *escapedPath = + (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)path, + NULL, + (CFStringRef)@":?=,!$&'()*+;[]@#~", + encoding); + + return [escapedPath autorelease]; +} + + +- (NSMutableURLRequest*)requestWithProtocol:(NSString*)protocol host:(NSString*)host path:(NSString*)path + parameters:(NSDictionary*)params { + + return [self requestWithProtocol:protocol host:host path:path parameters:params method:nil]; +} + + +- (NSMutableURLRequest*)requestWithProtocol:(NSString*)protocol host:(NSString*)host path:(NSString*)path + parameters:(NSDictionary*)params method:(NSString*)method { + + NSString* escapedPath = [DBRestClient escapePath:path]; + NSString* urlString = [NSString stringWithFormat:@"%@://%@/%@%@", + protocol, host, kDBDropboxAPIVersion, escapedPath]; + NSURL* url = [NSURL URLWithString:urlString]; + + NSArray* paramList = [session.credentialStore oauthParameters]; + if ([params count] > 0) { + NSArray* extraParams = [MPURLRequestParameter parametersFromDictionary:params]; + paramList = [paramList arrayByAddingObjectsFromArray:extraParams]; + } + MPOAuthURLRequest* oauthRequest = + [[[MPOAuthURLRequest alloc] initWithURL:url andParameters:paramList] autorelease]; + if (method) { + oauthRequest.HTTPMethod = method; + } + NSMutableURLRequest* urlRequest = [oauthRequest + urlRequestSignedWithSecret:session.credentialStore.signingKey + usingMethod:session.credentialStore.signatureMethod]; + return urlRequest; +} + + +- (void)checkForAuthenticationFailure:(DBRequest*)request { + if (request.error && request.error.code == 401 && [request.error.domain isEqual:@"dropbox.com"]) { + [session.delegate sessionDidReceiveAuthorizationFailure:session]; + } +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBSession.h b/Classes/ThirdParty/DropboxSDK/DBSession.h new file mode 100644 index 0000000..599a903 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBSession.h @@ -0,0 +1,44 @@ +// +// DBSession.h +// DropboxSDK +// +// Created by Brian Smith on 4/8/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "MPOAuthCredentialConcreteStore.h" + +extern NSString* kDBDropboxAPIHost; +extern NSString* kDBDropboxAPIContentHost; +extern NSString* kDBDropboxAPIVersion; + +@protocol DBSessionDelegate; + + +/* Creating and setting the shared DBSession should be done before any other Dropbox objects are + used, perferrably in the UIApplication delegate. */ +@interface DBSession : NSObject { + MPOAuthCredentialConcreteStore* credentialStore; + id delegate; +} + ++ (DBSession*)sharedSession; ++ (void)setSharedSession:(DBSession*)session; + +- (id)initWithConsumerKey:(NSString*)key consumerSecret:(NSString*)secret; +- (BOOL)isLinked; // If not linked, you can only call loginWithEmail:password from the DBRestClient + +- (void)updateAccessToken:(NSString*)token accessTokenSecret:(NSString*)secret; +- (void)unlink; + +@property (nonatomic, readonly) MPOAuthCredentialConcreteStore* credentialStore; +@property (nonatomic, assign) id delegate; + +@end + + +@protocol DBSessionDelegate + +- (void)sessionDidReceiveAuthorizationFailure:(DBSession*)session; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DBSession.m b/Classes/ThirdParty/DropboxSDK/DBSession.m new file mode 100644 index 0000000..af16ccd --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DBSession.m @@ -0,0 +1,117 @@ +// +// DBSession.m +// DropboxSDK +// +// Created by Brian Smith on 4/8/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "DBSession.h" +#import "MPOAuthCredentialConcreteStore.h" +#import "MPOAuthSignatureParameter.h" + + +NSString* kDBDropboxAPIHost = @"api.dropbox.com"; +NSString* kDBDropboxAPIContentHost = @"api-content.dropbox.com"; +NSString* kDBDropboxAPIVersion = @"0"; + +static DBSession* _sharedSession = nil; +static NSString* kDBDropboxSavedCredentialsKey = @"kDBDropboxSavedCredentialsKey"; + + +@interface DBSession () + +- (NSDictionary*)savedCredentials; +- (void)saveCredentials:(NSDictionary*)credentials; +- (void)clearSavedCredentials; + +@end + + +@implementation DBSession + ++ (DBSession*)sharedSession { + return _sharedSession; +} + ++ (void)setSharedSession:(DBSession*)session { + if (session == _sharedSession) return; + [_sharedSession release]; + _sharedSession = [session retain]; +} + +- (id)initWithConsumerKey:(NSString*)key consumerSecret:(NSString*)secret { + if ((self = [super init])) { + + NSMutableDictionary* credentials = + [NSMutableDictionary dictionaryWithObjectsAndKeys: + key, kMPOAuthCredentialConsumerKey, + secret, kMPOAuthCredentialConsumerSecret, + kMPOAuthSignatureMethodHMACSHA1, kMPOAuthSignatureMethod, nil]; + + NSDictionary* savedCredentials = [self savedCredentials]; + if (savedCredentials != nil) { + if ([key isEqualToString:[savedCredentials objectForKey:kMPOAuthCredentialConsumerKey]]) { + + [credentials setObject:[savedCredentials objectForKey:kMPOAuthCredentialAccessToken] + forKey:kMPOAuthCredentialAccessToken]; + [credentials setObject:[savedCredentials objectForKey:kMPOAuthCredentialAccessTokenSecret] + forKey:kMPOAuthCredentialAccessTokenSecret]; + } else { + [self clearSavedCredentials]; + } + } + + credentialStore = [[MPOAuthCredentialConcreteStore alloc] initWithCredentials:credentials]; + } + return self; +} + +- (void)dealloc { + [credentialStore release]; + [super dealloc]; +} + +@synthesize credentialStore; +@synthesize delegate; + +- (void)updateAccessToken:(NSString*)token accessTokenSecret:(NSString*)secret { + credentialStore.accessToken = token; + credentialStore.accessTokenSecret = secret; + NSDictionary* credentials = [NSDictionary dictionaryWithObjectsAndKeys: + credentialStore.consumerKey, kMPOAuthCredentialConsumerKey, + credentialStore.accessToken, kMPOAuthCredentialAccessToken, + credentialStore.accessTokenSecret, kMPOAuthCredentialAccessTokenSecret, + nil]; + [self saveCredentials:credentials]; +} + +- (BOOL) isLinked { + return credentialStore.accessToken != nil; +} + +- (void)unlink { + credentialStore.accessToken = nil; + credentialStore.accessTokenSecret = nil; + [self clearSavedCredentials]; +} + +#pragma mark private methods + +- (NSDictionary*)savedCredentials { + return [[NSUserDefaults standardUserDefaults] objectForKey:kDBDropboxSavedCredentialsKey]; +} + +- (void)saveCredentials:(NSDictionary*)credentials { + if (credentials == nil) return; + + [[NSUserDefaults standardUserDefaults] + setObject:credentials forKey:kDBDropboxSavedCredentialsKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +- (void)clearSavedCredentials { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:kDBDropboxSavedCredentialsKey]; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/DropboxSDK.h b/Classes/ThirdParty/DropboxSDK/DropboxSDK.h new file mode 100644 index 0000000..4973d62 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/DropboxSDK.h @@ -0,0 +1,17 @@ +/* + * DropboxSDK.h + * DropboxSDK + * + * Created by Brian Smith on 7/13/10. + * Copyright 2010 Dropbox, Inc. All rights reserved. + * + */ + +/* Import this file to get the most important header files imported */ +#import "DBSession.h" +#import "DBRestClient.h" +#import "DBLoginController.h" +#import "DBAccountInfo.h" +#import "DBMetadata.h" +#import "DBQuota.h" +#import "DBError.h" \ No newline at end of file diff --git a/Classes/ThirdParty/DropboxSDK/JSON/JSON.h b/Classes/ThirdParty/DropboxSDK/JSON/JSON.h new file mode 100644 index 0000000..1e58c9a --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/JSON.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + @mainpage A strict JSON parser and generator for Objective-C + + JSON (JavaScript Object Notation) is a lightweight data-interchange + format. This framework provides two apis for parsing and generating + JSON. One standard object-based and a higher level api consisting of + categories added to existing Objective-C classes. + + Learn more on the http://code.google.com/p/json-framework project site. + + This framework does its best to be as strict as possible, both in what it + accepts and what it generates. For example, it does not support trailing commas + in arrays or objects. Nor does it support embedded comments, or + anything else not in the JSON specification. This is considered a feature. + +*/ + +#import "SBJSON.h" +#import "NSObject+SBJSON.h" +#import "NSString+SBJSON.h" + diff --git a/Classes/ThirdParty/DropboxSDK/JSON/NSObject+SBJSON.h b/Classes/ThirdParty/DropboxSDK/JSON/NSObject+SBJSON.h new file mode 100644 index 0000000..ecf0ee4 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/NSObject+SBJSON.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + + +/** + @brief Adds JSON generation to Foundation classes + + This is a category on NSObject that adds methods for returning JSON representations + of standard objects to the objects themselves. This means you can call the + -JSONRepresentation method on an NSArray object and it'll do what you want. + */ +@interface NSObject (NSObject_SBJSON) + +/** + @brief Returns a string containing the receiver encoded as a JSON fragment. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + @li NSString + @li NSNumber (also used for booleans) + @li NSNull + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString *)JSONFragment; + +/** + @brief Returns a string containing the receiver encoded in JSON. + + This method is added as a category on NSObject but is only actually + supported for the following objects: + @li NSDictionary + @li NSArray + */ +- (NSString *)JSONRepresentation; + +@end + diff --git a/Classes/ThirdParty/DropboxSDK/JSON/NSObject+SBJSON.m b/Classes/ThirdParty/DropboxSDK/JSON/NSObject+SBJSON.m new file mode 100644 index 0000000..20b084b --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/NSObject+SBJSON.m @@ -0,0 +1,53 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "NSObject+SBJSON.h" +#import "SBJsonWriter.h" + +@implementation NSObject (NSObject_SBJSON) + +- (NSString *)JSONFragment { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithFragment:self]; + if (!json) + NSLog(@"-JSONFragment failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +- (NSString *)JSONRepresentation { + SBJsonWriter *jsonWriter = [SBJsonWriter new]; + NSString *json = [jsonWriter stringWithObject:self]; + if (!json) + NSLog(@"-JSONRepresentation failed. Error trace is: %@", [jsonWriter errorTrace]); + [jsonWriter release]; + return json; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/JSON/NSString+SBJSON.h b/Classes/ThirdParty/DropboxSDK/JSON/NSString+SBJSON.h new file mode 100644 index 0000000..fad7179 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/NSString+SBJSON.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +/** + @brief Adds JSON parsing methods to NSString + +This is a category on NSString that adds methods for parsing the target string. +*/ +@interface NSString (NSString_SBJSON) + + +/** + @brief Returns the object represented in the receiver, or nil on error. + + Returns a a scalar object represented by the string's JSON fragment representation. + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)JSONFragmentValue; + +/** + @brief Returns the NSDictionary or NSArray represented by the current string's JSON representation. + + Returns the dictionary or array represented in the receiver, or nil on error. + + Returns the NSDictionary or NSArray represented by the current string's JSON representation. + */ +- (id)JSONValue; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/JSON/NSString+SBJSON.m b/Classes/ThirdParty/DropboxSDK/JSON/NSString+SBJSON.m new file mode 100644 index 0000000..41a5a85 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/NSString+SBJSON.m @@ -0,0 +1,55 @@ +/* + Copyright (C) 2007-2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "NSString+SBJSON.h" +#import "SBJsonParser.h" + +@implementation NSString (NSString_SBJSON) + +- (id)JSONFragmentValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser fragmentWithString:self]; + if (!repr) + NSLog(@"-JSONFragmentValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +- (id)JSONValue +{ + SBJsonParser *jsonParser = [SBJsonParser new]; + id repr = [jsonParser objectWithString:self]; + if (!repr) + NSLog(@"-JSONValue failed. Error trace is: %@", [jsonParser errorTrace]); + [jsonParser release]; + return repr; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/JSON/SBJSON.h b/Classes/ThirdParty/DropboxSDK/JSON/SBJSON.h new file mode 100644 index 0000000..43d63c3 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/SBJSON.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2007-2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import "SBJsonParser.h" +#import "SBJsonWriter.h" + +/** + @brief Facade for SBJsonWriter/SBJsonParser. + + Requests are forwarded to instances of SBJsonWriter and SBJsonParser. + */ +@interface SBJSON : SBJsonBase { + +@private + SBJsonParser *jsonParser; + SBJsonWriter *jsonWriter; +} + + +/// Return the fragment represented by the given string +- (id)fragmentWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Return the object represented by the given string +- (id)objectWithString:(NSString*)jsonrep + error:(NSError**)error; + +/// Parse the string and return the represented object (or scalar) +- (id)objectWithString:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +/// Return JSON representation of an array or dictionary +- (NSString*)stringWithObject:(id)value + error:(NSError**)error; + +/// Return JSON representation of any legal JSON value +- (NSString*)stringWithFragment:(id)value + error:(NSError**)error; + +/// Return JSON representation (or fragment) for the given object +- (NSString*)stringWithObject:(id)value + allowScalar:(BOOL)x + error:(NSError**)error; + + +@end diff --git a/Classes/ThirdParty/DropboxSDK/JSON/SBJSON.m b/Classes/ThirdParty/DropboxSDK/JSON/SBJSON.m new file mode 100644 index 0000000..2a30f1a --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/SBJSON.m @@ -0,0 +1,212 @@ +/* + Copyright (C) 2007-2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJSON.h" + +@implementation SBJSON + +- (id)init { + self = [super init]; + if (self) { + jsonWriter = [SBJsonWriter new]; + jsonParser = [SBJsonParser new]; + [self setMaxDepth:512]; + + } + return self; +} + +- (void)dealloc { + [jsonWriter release]; + [jsonParser release]; + [super dealloc]; +} + +#pragma mark Writer + + +- (NSString *)stringWithObject:(id)obj { + NSString *repr = [jsonWriter stringWithObject:obj]; + if (repr) + return repr; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param allowScalar wether to return json fragments for scalar objects + @param error used to return an error by reference (pass NULL if this is not desired) + +@deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithObject:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + NSString *json = allowScalar ? [jsonWriter stringWithFragment:value] : [jsonWriter stringWithObject:value]; + if (json) + return json; + + [errorTrace release]; + errorTrace = [[jsonWriter errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (NSString*)stringWithFragment:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:YES + error:error]; +} + +/** + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p error can be interrogated to find the cause of the error. + + @param value a NSDictionary or NSArray instance + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (NSString*)stringWithObject:(id)value error:(NSError**)error { + return [self stringWithObject:value + allowScalar:NO + error:error]; +} + +#pragma mark Parsing + +- (id)objectWithString:(NSString *)repr { + id obj = [jsonParser objectWithString:repr]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + return nil; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object can be + a string, number, boolean, null, array or dictionary. + + @param value the json string to parse + @param allowScalar whether to return objects for JSON fragments + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)objectWithString:(id)value allowScalar:(BOOL)allowScalar error:(NSError**)error { + + id obj = allowScalar ? [jsonParser fragmentWithString:value] : [jsonParser objectWithString:value]; + if (obj) + return obj; + + [errorTrace release]; + errorTrace = [[jsonParser errorTrace] mutableCopy]; + + if (error) + *error = [errorTrace lastObject]; + return nil; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object can be + a string, number, boolean, null, array or dictionary. + + @param repr the json string to parse + @param error used to return an error by reference (pass NULL if this is not desired) + + @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed. + */ +- (id)fragmentWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:YES + error:error]; +} + +/** + Returns the object represented by the passed-in string or nil on error. The returned object + will be either a dictionary or an array. + + @param repr the json string to parse + @param error used to return an error by reference (pass NULL if this is not desired) + */ +- (id)objectWithString:(NSString*)repr error:(NSError**)error { + return [self objectWithString:repr + allowScalar:NO + error:error]; +} + + + +#pragma mark Properties - parsing + +- (NSUInteger)maxDepth { + return jsonParser.maxDepth; +} + +- (void)setMaxDepth:(NSUInteger)d { + jsonWriter.maxDepth = jsonParser.maxDepth = d; +} + + +#pragma mark Properties - writing + +- (BOOL)humanReadable { + return jsonWriter.humanReadable; +} + +- (void)setHumanReadable:(BOOL)x { + jsonWriter.humanReadable = x; +} + +- (BOOL)sortKeys { + return jsonWriter.sortKeys; +} + +- (void)setSortKeys:(BOOL)x { + jsonWriter.sortKeys = x; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/JSON/SBJsonBase.h b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonBase.h new file mode 100644 index 0000000..7b10844 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonBase.h @@ -0,0 +1,86 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +extern NSString * SBJSONErrorDomain; + + +enum { + EUNSUPPORTED = 1, + EPARSENUM, + EPARSE, + EFRAGMENT, + ECTRL, + EUNICODE, + EDEPTH, + EESCAPE, + ETRAILCOMMA, + ETRAILGARBAGE, + EEOF, + EINPUT +}; + +/** + @brief Common base class for parsing & writing. + + This class contains the common error-handling code and option between the parser/writer. + */ +@interface SBJsonBase : NSObject { + NSMutableArray *errorTrace; + +@protected + NSUInteger depth, maxDepth; +} + +/** + @brief The maximum recursing depth. + + Defaults to 512. If the input is nested deeper than this the input will be deemed to be + malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can + turn off this security feature by setting the maxDepth value to 0. + */ +@property NSUInteger maxDepth; + +/** + @brief Return an error trace, or nil if there was no errors. + + Note that this method returns the trace of the last method that failed. + You need to check the return value of the call you're making to figure out + if the call actually failed, before you know call this method. + */ + @property(copy,readonly) NSArray* errorTrace; + +/// @internal for use in subclasses to add errors to the stack trace +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str; + +/// @internal for use in subclasess to clear the error before a new parsing attempt +- (void)clearErrorTrace; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/JSON/SBJsonBase.m b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonBase.m new file mode 100644 index 0000000..6684325 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonBase.m @@ -0,0 +1,78 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJsonBase.h" +NSString * SBJSONErrorDomain = @"org.brautaset.JSON.ErrorDomain"; + + +@implementation SBJsonBase + +@synthesize errorTrace; +@synthesize maxDepth; + +- (id)init { + self = [super init]; + if (self) + self.maxDepth = 512; + return self; +} + +- (void)dealloc { + [errorTrace release]; + [super dealloc]; +} + +- (void)addErrorWithCode:(NSUInteger)code description:(NSString*)str { + NSDictionary *userInfo; + if (!errorTrace) { + errorTrace = [NSMutableArray new]; + userInfo = [NSDictionary dictionaryWithObject:str forKey:NSLocalizedDescriptionKey]; + + } else { + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + str, NSLocalizedDescriptionKey, + [errorTrace lastObject], NSUnderlyingErrorKey, + nil]; + } + + NSError *error = [NSError errorWithDomain:SBJSONErrorDomain code:code userInfo:userInfo]; + + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace addObject:error]; + [self didChangeValueForKey:@"errorTrace"]; +} + +- (void)clearErrorTrace { + [self willChangeValueForKey:@"errorTrace"]; + [errorTrace release]; + errorTrace = nil; + [self didChangeValueForKey:@"errorTrace"]; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/JSON/SBJsonParser.h b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonParser.h new file mode 100644 index 0000000..e95304d --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonParser.h @@ -0,0 +1,87 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import "SBJsonBase.h" + +/** + @brief Options for the parser class. + + This exists so the SBJSON facade can implement the options in the parser without having to re-declare them. + */ +@protocol SBJsonParser + +/** + @brief Return the object represented by the given string. + + Returns the object represented by the passed-in string or nil on error. The returned object can be + a string, number, boolean, null, array or dictionary. + + @param repr the json string to parse + */ +- (id)objectWithString:(NSString *)repr; + +@end + + +/** + @brief The JSON parser class. + + JSON is mapped to Objective-C types in the following way: + + @li Null -> NSNull + @li String -> NSMutableString + @li Array -> NSMutableArray + @li Object -> NSMutableDictionary + @li Boolean -> NSNumber (initialised with -initWithBool:) + @li Number -> NSDecimalNumber + + Since Objective-C doesn't have a dedicated class for boolean values, these turns into NSNumber + instances. These are initialised with the -initWithBool: method, and + round-trip back to JSON properly. (They won't silently suddenly become 0 or 1; they'll be + represented as 'true' and 'false' again.) + + JSON numbers turn into NSDecimalNumber instances, + as we can thus avoid any loss of precision. (JSON allows ridiculously large numbers.) + + */ +@interface SBJsonParser : SBJsonBase { + +@private + const char *c; +} + +@end + +// don't use - exists for backwards compatibility with 2.1.x only. Will be removed in 2.3. +@interface SBJsonParser (Private) +- (id)fragmentWithString:(id)repr; +@end + + diff --git a/Classes/ThirdParty/DropboxSDK/JSON/SBJsonParser.m b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonParser.m new file mode 100644 index 0000000..eda051a --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonParser.m @@ -0,0 +1,475 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJsonParser.h" + +@interface SBJsonParser () + +- (BOOL)scanValue:(NSObject **)o; + +- (BOOL)scanRestOfArray:(NSMutableArray **)o; +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o; +- (BOOL)scanRestOfNull:(NSNull **)o; +- (BOOL)scanRestOfFalse:(NSNumber **)o; +- (BOOL)scanRestOfTrue:(NSNumber **)o; +- (BOOL)scanRestOfString:(NSMutableString **)o; + +// Cannot manage without looking at the first digit +- (BOOL)scanNumber:(NSNumber **)o; + +- (BOOL)scanHexQuad:(unichar *)x; +- (BOOL)scanUnicodeChar:(unichar *)x; + +- (BOOL)scanIsAtEnd; + +@end + +#define skipWhitespace(c) while (isspace(*c)) c++ +#define skipDigits(c) while (isdigit(*c)) c++ + + +@implementation SBJsonParser + +static char ctrl[0x22]; + + ++ (void)initialize { + ctrl[0] = '\"'; + ctrl[1] = '\\'; + for (int i = 1; i < 0x20; i++) + ctrl[i+1] = i; + ctrl[0x21] = 0; +} + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (id)fragmentWithString:(id)repr { + [self clearErrorTrace]; + + if (!repr) { + [self addErrorWithCode:EINPUT description:@"Input was 'nil'"]; + return nil; + } + + depth = 0; + c = [repr UTF8String]; + + id o; + if (![self scanValue:&o]) { + return nil; + } + + // We found some valid JSON. But did it also contain something else? + if (![self scanIsAtEnd]) { + [self addErrorWithCode:ETRAILGARBAGE description:@"Garbage after JSON"]; + return nil; + } + + NSAssert1(o, @"Should have a valid object from %@", repr); + return o; +} + +- (id)objectWithString:(NSString *)repr { + + id o = [self fragmentWithString:repr]; + if (!o) + return nil; + + // Check that the object we've found is a valid JSON container. + if (![o isKindOfClass:[NSDictionary class]] && ![o isKindOfClass:[NSArray class]]) { + [self addErrorWithCode:EFRAGMENT description:@"Valid fragment, but not JSON"]; + return nil; + } + + return o; +} + +/* + In contrast to the public methods, it is an error to omit the error parameter here. + */ +- (BOOL)scanValue:(NSObject **)o +{ + skipWhitespace(c); + + switch (*c++) { + case '{': + return [self scanRestOfDictionary:(NSMutableDictionary **)o]; + break; + case '[': + return [self scanRestOfArray:(NSMutableArray **)o]; + break; + case '"': + return [self scanRestOfString:(NSMutableString **)o]; + break; + case 'f': + return [self scanRestOfFalse:(NSNumber **)o]; + break; + case 't': + return [self scanRestOfTrue:(NSNumber **)o]; + break; + case 'n': + return [self scanRestOfNull:(NSNull **)o]; + break; + case '-': + case '0'...'9': + c--; // cannot verify number correctly without the first character + return [self scanNumber:(NSNumber **)o]; + break; + case '+': + [self addErrorWithCode:EPARSENUM description: @"Leading + disallowed in number"]; + return NO; + break; + case 0x0: + [self addErrorWithCode:EEOF description:@"Unexpected end of string"]; + return NO; + break; + default: + [self addErrorWithCode:EPARSE description: @"Unrecognised leading character"]; + return NO; + break; + } + + NSAssert(0, @"Should never get here"); + return NO; +} + +- (BOOL)scanRestOfTrue:(NSNumber **)o +{ + if (!strncmp(c, "rue", 3)) { + c += 3; + *o = [NSNumber numberWithBool:YES]; + return YES; + } + [self addErrorWithCode:EPARSE description:@"Expected 'true'"]; + return NO; +} + +- (BOOL)scanRestOfFalse:(NSNumber **)o +{ + if (!strncmp(c, "alse", 4)) { + c += 4; + *o = [NSNumber numberWithBool:NO]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'false'"]; + return NO; +} + +- (BOOL)scanRestOfNull:(NSNull **)o { + if (!strncmp(c, "ull", 3)) { + c += 3; + *o = [NSNull null]; + return YES; + } + [self addErrorWithCode:EPARSE description: @"Expected 'null'"]; + return NO; +} + +- (BOOL)scanRestOfArray:(NSMutableArray **)o { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableArray arrayWithCapacity:8]; + + for (; *c ;) { + id v; + + skipWhitespace(c); + if (*c == ']' && c++) { + depth--; + return YES; + } + + if (![self scanValue:&v]) { + [self addErrorWithCode:EPARSE description:@"Expected value while parsing array"]; + return NO; + } + + [*o addObject:v]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == ']') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in array"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing array"]; + return NO; +} + +- (BOOL)scanRestOfDictionary:(NSMutableDictionary **)o +{ + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + + *o = [NSMutableDictionary dictionaryWithCapacity:7]; + + for (; *c ;) { + id k, v; + + skipWhitespace(c); + if (*c == '}' && c++) { + depth--; + return YES; + } + + if (!(*c == '\"' && c++ && [self scanRestOfString:&k])) { + [self addErrorWithCode:EPARSE description: @"Object key string expected"]; + return NO; + } + + skipWhitespace(c); + if (*c != ':') { + [self addErrorWithCode:EPARSE description: @"Expected ':' separating key and value"]; + return NO; + } + + c++; + if (![self scanValue:&v]) { + NSString *string = [NSString stringWithFormat:@"Object value expected for key: %@", k]; + [self addErrorWithCode:EPARSE description: string]; + return NO; + } + + [*o setObject:v forKey:k]; + + skipWhitespace(c); + if (*c == ',' && c++) { + skipWhitespace(c); + if (*c == '}') { + [self addErrorWithCode:ETRAILCOMMA description: @"Trailing comma disallowed in object"]; + return NO; + } + } + } + + [self addErrorWithCode:EEOF description: @"End of input while parsing object"]; + return NO; +} + +- (BOOL)scanRestOfString:(NSMutableString **)o +{ + *o = [NSMutableString stringWithCapacity:16]; + do { + // First see if there's a portion we can grab in one go. + // Doing this caused a massive speedup on the long string. + size_t len = strcspn(c, ctrl); + if (len) { + // check for + id t = [[NSString alloc] initWithBytesNoCopy:(char*)c + length:len + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + if (t) { + [*o appendString:t]; + [t release]; + c += len; + } + } + + if (*c == '"') { + c++; + return YES; + + } else if (*c == '\\') { + unichar uc = *++c; + switch (uc) { + case '\\': + case '/': + case '"': + break; + + case 'b': uc = '\b'; break; + case 'n': uc = '\n'; break; + case 'r': uc = '\r'; break; + case 't': uc = '\t'; break; + case 'f': uc = '\f'; break; + + case 'u': + c++; + if (![self scanUnicodeChar:&uc]) { + [self addErrorWithCode:EUNICODE description: @"Broken unicode character"]; + return NO; + } + c--; // hack. + break; + default: + [self addErrorWithCode:EESCAPE description: [NSString stringWithFormat:@"Illegal escape sequence '0x%x'", uc]]; + return NO; + break; + } + CFStringAppendCharacters((CFMutableStringRef)*o, &uc, 1); + c++; + + } else if (*c < 0x20) { + [self addErrorWithCode:ECTRL description: [NSString stringWithFormat:@"Unescaped control character '0x%x'", *c]]; + return NO; + + } else { + NSLog(@"should not be able to get here"); + } + } while (*c); + + [self addErrorWithCode:EEOF description:@"Unexpected EOF while parsing string"]; + return NO; +} + +- (BOOL)scanUnicodeChar:(unichar *)x +{ + unichar hi, lo; + + if (![self scanHexQuad:&hi]) { + [self addErrorWithCode:EUNICODE description: @"Missing hex quad"]; + return NO; + } + + if (hi >= 0xd800) { // high surrogate char? + if (hi < 0xdc00) { // yes - expect a low char + + if (!(*c == '\\' && ++c && *c == 'u' && ++c && [self scanHexQuad:&lo])) { + [self addErrorWithCode:EUNICODE description: @"Missing low character in surrogate pair"]; + return NO; + } + + if (lo < 0xdc00 || lo >= 0xdfff) { + [self addErrorWithCode:EUNICODE description:@"Invalid low surrogate char"]; + return NO; + } + + hi = (hi - 0xd800) * 0x400 + (lo - 0xdc00) + 0x10000; + + } else if (hi < 0xe000) { + [self addErrorWithCode:EUNICODE description:@"Invalid high character in surrogate pair"]; + return NO; + } + } + + *x = hi; + return YES; +} + +- (BOOL)scanHexQuad:(unichar *)x +{ + *x = 0; + for (int i = 0; i < 4; i++) { + unichar uc = *c; + c++; + int d = (uc >= '0' && uc <= '9') + ? uc - '0' : (uc >= 'a' && uc <= 'f') + ? (uc - 'a' + 10) : (uc >= 'A' && uc <= 'F') + ? (uc - 'A' + 10) : -1; + if (d == -1) { + [self addErrorWithCode:EUNICODE description:@"Missing hex digit in quad"]; + return NO; + } + *x *= 16; + *x += d; + } + return YES; +} + +- (BOOL)scanNumber:(NSNumber **)o +{ + const char *ns = c; + + // The logic to test for validity of the number formatting is relicensed + // from JSON::XS with permission from its author Marc Lehmann. + // (Available at the CPAN: http://search.cpan.org/dist/JSON-XS/ .) + + if ('-' == *c) + c++; + + if ('0' == *c && c++) { + if (isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"Leading 0 disallowed in number"]; + return NO; + } + + } else if (!isdigit(*c) && c != ns) { + [self addErrorWithCode:EPARSENUM description: @"No digits after initial minus"]; + return NO; + + } else { + skipDigits(c); + } + + // Fractional part + if ('.' == *c && c++) { + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after decimal point"]; + return NO; + } + skipDigits(c); + } + + // Exponential part + if ('e' == *c || 'E' == *c) { + c++; + + if ('-' == *c || '+' == *c) + c++; + + if (!isdigit(*c)) { + [self addErrorWithCode:EPARSENUM description: @"No digits after exponent"]; + return NO; + } + skipDigits(c); + } + + id str = [[NSString alloc] initWithBytesNoCopy:(char*)ns + length:c - ns + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + [str autorelease]; + if (str && (*o = [NSDecimalNumber decimalNumberWithString:str])) + return YES; + + [self addErrorWithCode:EPARSENUM description: @"Failed creating decimal instance"]; + return NO; +} + +- (BOOL)scanIsAtEnd +{ + skipWhitespace(c); + return !*c; +} + + +@end diff --git a/Classes/ThirdParty/DropboxSDK/JSON/SBJsonWriter.h b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonWriter.h new file mode 100644 index 0000000..f6f5e17 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonWriter.h @@ -0,0 +1,129 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import "SBJsonBase.h" + +/** + @brief Options for the writer class. + + This exists so the SBJSON facade can implement the options in the writer without having to re-declare them. + */ +@protocol SBJsonWriter + +/** + @brief Whether we are generating human-readable (multiline) JSON. + + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/** + @brief Whether or not to sort the dictionary keys in the output. + + If this is set to YES, the dictionary keys in the JSON output will be in sorted order. + (This is useful if you need to compare two structures, for example.) The default is NO. + */ +@property BOOL sortKeys; + +/** + @brief Return JSON representation (or fragment) for the given object. + + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and @p error is not NULL, @p *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as a JSON fragment + + */ +- (NSString*)stringWithObject:(id)value; + +@end + + +/** + @brief The JSON writer class. + + Objective-C types are mapped to JSON types in the following way: + + @li NSNull -> Null + @li NSString -> String + @li NSArray -> Array + @li NSDictionary -> Object + @li NSNumber (-initWithBool:) -> Boolean + @li NSNumber -> Number + + In JSON the keys of an object must be strings. NSDictionary keys need + not be, but attempting to convert an NSDictionary with non-string keys + into JSON will throw an exception. + + NSNumber instances created with the +initWithBool: method are + converted into the JSON boolean "true" and "false" values, and vice + versa. Any other NSNumber instances are converted to a JSON number the + way you would expect. + + */ +@interface SBJsonWriter : SBJsonBase { + +@private + BOOL sortKeys, humanReadable; +} + +@end + +// don't use - exists for backwards compatibility. Will be removed in 2.3. +@interface SBJsonWriter (Private) +- (NSString*)stringWithFragment:(id)value; +@end + +/** + @brief Allows generation of JSON for otherwise unsupported classes. + + If you have a custom class that you want to create a JSON representation for you can implement + this method in your class. It should return a representation of your object defined + in terms of objects that can be translated into JSON. For example, a Person + object might implement it like this: + + @code + - (id)jsonProxyObject { + return [NSDictionary dictionaryWithObjectsAndKeys: + name, @"name", + phone, @"phone", + email, @"email", + nil]; + } + @endcode + + */ +@interface NSObject (SBProxyForJson) +- (id)proxyForJson; +@end + diff --git a/Classes/ThirdParty/DropboxSDK/JSON/SBJsonWriter.m b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonWriter.m new file mode 100644 index 0000000..0f32904 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/JSON/SBJsonWriter.m @@ -0,0 +1,237 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJsonWriter.h" + +@interface SBJsonWriter () + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json; +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json; +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json; +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json; + +- (NSString*)indent; + +@end + +@implementation SBJsonWriter + +static NSMutableCharacterSet *kEscapeChars; + ++ (void)initialize { + kEscapeChars = [[NSMutableCharacterSet characterSetWithRange: NSMakeRange(0,32)] retain]; + [kEscapeChars addCharactersInString: @"\"\\"]; +} + + +@synthesize sortKeys; +@synthesize humanReadable; + +/** + @deprecated This exists in order to provide fragment support in older APIs in one more version. + It should be removed in the next major version. + */ +- (NSString*)stringWithFragment:(id)value { + [self clearErrorTrace]; + depth = 0; + NSMutableString *json = [NSMutableString stringWithCapacity:128]; + + if ([self appendValue:value into:json]) + return json; + + return nil; +} + + +- (NSString*)stringWithObject:(id)value { + + if ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]]) { + return [self stringWithFragment:value]; + } + + if ([value respondsToSelector:@selector(proxyForJson)]) { + NSString *tmp = [self stringWithObject:[value proxyForJson]]; + if (tmp) + return tmp; + } + + + [self clearErrorTrace]; + [self addErrorWithCode:EFRAGMENT description:@"Not valid type for JSON"]; + return nil; +} + + +- (NSString*)indent { + return [@"\n" stringByPaddingToLength:1 + 2 * depth withString:@" " startingAtIndex:0]; +} + +- (BOOL)appendValue:(id)fragment into:(NSMutableString*)json { + if ([fragment isKindOfClass:[NSDictionary class]]) { + if (![self appendDictionary:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSArray class]]) { + if (![self appendArray:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSString class]]) { + if (![self appendString:fragment into:json]) + return NO; + + } else if ([fragment isKindOfClass:[NSNumber class]]) { + if ('c' == *[fragment objCType]) + [json appendString:[fragment boolValue] ? @"true" : @"false"]; + else + [json appendString:[fragment stringValue]]; + + } else if ([fragment isKindOfClass:[NSNull class]]) { + [json appendString:@"null"]; + } else if ([fragment respondsToSelector:@selector(proxyForJson)]) { + [self appendValue:[fragment proxyForJson] into:json]; + + } else { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"JSON serialisation not supported for %@", [fragment class]]]; + return NO; + } + return YES; +} + +- (BOOL)appendArray:(NSArray*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"["]; + + BOOL addComma = NO; + for (id value in fragment) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![self appendValue:value into:json]) { + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"]"]; + return YES; +} + +- (BOOL)appendDictionary:(NSDictionary*)fragment into:(NSMutableString*)json { + if (maxDepth && ++depth > maxDepth) { + [self addErrorWithCode:EDEPTH description: @"Nested too deep"]; + return NO; + } + [json appendString:@"{"]; + + NSString *colon = [self humanReadable] ? @" : " : @":"; + BOOL addComma = NO; + NSArray *keys = [fragment allKeys]; + if (self.sortKeys) + keys = [keys sortedArrayUsingSelector:@selector(compare:)]; + + for (id value in keys) { + if (addComma) + [json appendString:@","]; + else + addComma = YES; + + if ([self humanReadable]) + [json appendString:[self indent]]; + + if (![value isKindOfClass:[NSString class]]) { + [self addErrorWithCode:EUNSUPPORTED description: @"JSON object key must be string"]; + return NO; + } + + if (![self appendString:value into:json]) + return NO; + + [json appendString:colon]; + if (![self appendValue:[fragment objectForKey:value] into:json]) { + [self addErrorWithCode:EUNSUPPORTED description:[NSString stringWithFormat:@"Unsupported value for key %@ in object", value]]; + return NO; + } + } + + depth--; + if ([self humanReadable] && [fragment count]) + [json appendString:[self indent]]; + [json appendString:@"}"]; + return YES; +} + +- (BOOL)appendString:(NSString*)fragment into:(NSMutableString*)json { + + [json appendString:@"\""]; + + NSRange esc = [fragment rangeOfCharacterFromSet:kEscapeChars]; + if ( !esc.length ) { + // No special chars -- can just add the raw string: + [json appendString:fragment]; + + } else { + NSUInteger length = [fragment length]; + for (NSUInteger i = 0; i < length; i++) { + unichar uc = [fragment characterAtIndex:i]; + switch (uc) { + case '"': [json appendString:@"\\\""]; break; + case '\\': [json appendString:@"\\\\"]; break; + case '\t': [json appendString:@"\\t"]; break; + case '\n': [json appendString:@"\\n"]; break; + case '\r': [json appendString:@"\\r"]; break; + case '\b': [json appendString:@"\\b"]; break; + case '\f': [json appendString:@"\\f"]; break; + default: + if (uc < 0x20) { + [json appendFormat:@"\\u%04x", uc]; + } else { + CFStringAppendCharacters((CFMutableStringRef)json, &uc, 1); + } + break; + + } + } + } + + [json appendString:@"\""]; + return YES; +} + + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/Crypto/Base64Transcoder.c b/Classes/ThirdParty/DropboxSDK/MPOAuth/Crypto/Base64Transcoder.c new file mode 100644 index 0000000..68d7774 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/Crypto/Base64Transcoder.c @@ -0,0 +1,230 @@ +/* + * Base64Transcoder.c + * Base64Test + * + * Created by Jonathan Wight on Tue Mar 18 2003. + * Copyright (c) 2003 Toxic Software. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "Base64Transcoder.h" + +#include +#include + +const u_int8_t kBase64EncodeTable[64] = { + /* 0 */ 'A', /* 1 */ 'B', /* 2 */ 'C', /* 3 */ 'D', + /* 4 */ 'E', /* 5 */ 'F', /* 6 */ 'G', /* 7 */ 'H', + /* 8 */ 'I', /* 9 */ 'J', /* 10 */ 'K', /* 11 */ 'L', + /* 12 */ 'M', /* 13 */ 'N', /* 14 */ 'O', /* 15 */ 'P', + /* 16 */ 'Q', /* 17 */ 'R', /* 18 */ 'S', /* 19 */ 'T', + /* 20 */ 'U', /* 21 */ 'V', /* 22 */ 'W', /* 23 */ 'X', + /* 24 */ 'Y', /* 25 */ 'Z', /* 26 */ 'a', /* 27 */ 'b', + /* 28 */ 'c', /* 29 */ 'd', /* 30 */ 'e', /* 31 */ 'f', + /* 32 */ 'g', /* 33 */ 'h', /* 34 */ 'i', /* 35 */ 'j', + /* 36 */ 'k', /* 37 */ 'l', /* 38 */ 'm', /* 39 */ 'n', + /* 40 */ 'o', /* 41 */ 'p', /* 42 */ 'q', /* 43 */ 'r', + /* 44 */ 's', /* 45 */ 't', /* 46 */ 'u', /* 47 */ 'v', + /* 48 */ 'w', /* 49 */ 'x', /* 50 */ 'y', /* 51 */ 'z', + /* 52 */ '0', /* 53 */ '1', /* 54 */ '2', /* 55 */ '3', + /* 56 */ '4', /* 57 */ '5', /* 58 */ '6', /* 59 */ '7', + /* 60 */ '8', /* 61 */ '9', /* 62 */ '+', /* 63 */ '/' +}; + +/* +-1 = Base64 end of data marker. +-2 = White space (tabs, cr, lf, space) +-3 = Noise (all non whitespace, non-base64 characters) +-4 = Dangerous noise +-5 = Illegal noise (null byte) +*/ + +const int8_t kBase64DecodeTable[128] = { + /* 0x00 */ -5, /* 0x01 */ -3, /* 0x02 */ -3, /* 0x03 */ -3, + /* 0x04 */ -3, /* 0x05 */ -3, /* 0x06 */ -3, /* 0x07 */ -3, + /* 0x08 */ -3, /* 0x09 */ -2, /* 0x0a */ -2, /* 0x0b */ -2, + /* 0x0c */ -2, /* 0x0d */ -2, /* 0x0e */ -3, /* 0x0f */ -3, + /* 0x10 */ -3, /* 0x11 */ -3, /* 0x12 */ -3, /* 0x13 */ -3, + /* 0x14 */ -3, /* 0x15 */ -3, /* 0x16 */ -3, /* 0x17 */ -3, + /* 0x18 */ -3, /* 0x19 */ -3, /* 0x1a */ -3, /* 0x1b */ -3, + /* 0x1c */ -3, /* 0x1d */ -3, /* 0x1e */ -3, /* 0x1f */ -3, + /* ' ' */ -2, /* '!' */ -3, /* '"' */ -3, /* '#' */ -3, + /* '$' */ -3, /* '%' */ -3, /* '&' */ -3, /* ''' */ -3, + /* '(' */ -3, /* ')' */ -3, /* '*' */ -3, /* '+' */ 62, + /* ',' */ -3, /* '-' */ -3, /* '.' */ -3, /* '/' */ 63, + /* '0' */ 52, /* '1' */ 53, /* '2' */ 54, /* '3' */ 55, + /* '4' */ 56, /* '5' */ 57, /* '6' */ 58, /* '7' */ 59, + /* '8' */ 60, /* '9' */ 61, /* ':' */ -3, /* ';' */ -3, + /* '<' */ -3, /* '=' */ -1, /* '>' */ -3, /* '?' */ -3, + /* '@' */ -3, /* 'A' */ 0, /* 'B' */ 1, /* 'C' */ 2, + /* 'D' */ 3, /* 'E' */ 4, /* 'F' */ 5, /* 'G' */ 6, + /* 'H' */ 7, /* 'I' */ 8, /* 'J' */ 9, /* 'K' */ 10, + /* 'L' */ 11, /* 'M' */ 12, /* 'N' */ 13, /* 'O' */ 14, + /* 'P' */ 15, /* 'Q' */ 16, /* 'R' */ 17, /* 'S' */ 18, + /* 'T' */ 19, /* 'U' */ 20, /* 'V' */ 21, /* 'W' */ 22, + /* 'X' */ 23, /* 'Y' */ 24, /* 'Z' */ 25, /* '[' */ -3, + /* '\' */ -3, /* ']' */ -3, /* '^' */ -3, /* '_' */ -3, + /* '`' */ -3, /* 'a' */ 26, /* 'b' */ 27, /* 'c' */ 28, + /* 'd' */ 29, /* 'e' */ 30, /* 'f' */ 31, /* 'g' */ 32, + /* 'h' */ 33, /* 'i' */ 34, /* 'j' */ 35, /* 'k' */ 36, + /* 'l' */ 37, /* 'm' */ 38, /* 'n' */ 39, /* 'o' */ 40, + /* 'p' */ 41, /* 'q' */ 42, /* 'r' */ 43, /* 's' */ 44, + /* 't' */ 45, /* 'u' */ 46, /* 'v' */ 47, /* 'w' */ 48, + /* 'x' */ 49, /* 'y' */ 50, /* 'z' */ 51, /* '{' */ -3, + /* '|' */ -3, /* '}' */ -3, /* '~' */ -3, /* 0x7f */ -3 +}; + +const u_int8_t kBits_00000011 = 0x03; +const u_int8_t kBits_00001111 = 0x0F; +const u_int8_t kBits_00110000 = 0x30; +const u_int8_t kBits_00111100 = 0x3C; +const u_int8_t kBits_00111111 = 0x3F; +const u_int8_t kBits_11000000 = 0xC0; +const u_int8_t kBits_11110000 = 0xF0; +const u_int8_t kBits_11111100 = 0xFC; + +size_t EstimateBas64EncodedDataSize(size_t inDataSize) +{ +size_t theEncodedDataSize = (int)ceil(inDataSize / 3.0) * 4; +theEncodedDataSize = theEncodedDataSize / 72 * 74 + theEncodedDataSize % 72; +return(theEncodedDataSize); +} + +size_t EstimateBas64DecodedDataSize(size_t inDataSize) +{ +size_t theDecodedDataSize = (int)ceil(inDataSize / 4.0) * 3; +//theDecodedDataSize = theDecodedDataSize / 72 * 74 + theDecodedDataSize % 72; +return(theDecodedDataSize); +} + +bool Base64EncodeData(const void *inInputData, size_t inInputDataSize, char *outOutputData, size_t *ioOutputDataSize) +{ +size_t theEncodedDataSize = EstimateBas64EncodedDataSize(inInputDataSize); +if (*ioOutputDataSize < theEncodedDataSize) + return(false); +*ioOutputDataSize = theEncodedDataSize; +const u_int8_t *theInPtr = (const u_int8_t *)inInputData; +u_int32_t theInIndex = 0, theOutIndex = 0; +for (; theInIndex < (inInputDataSize / 3) * 3; theInIndex += 3) + { + outOutputData[theOutIndex++] = kBase64EncodeTable[(theInPtr[theInIndex] & kBits_11111100) >> 2]; + outOutputData[theOutIndex++] = kBase64EncodeTable[(theInPtr[theInIndex] & kBits_00000011) << 4 | (theInPtr[theInIndex + 1] & kBits_11110000) >> 4]; + outOutputData[theOutIndex++] = kBase64EncodeTable[(theInPtr[theInIndex + 1] & kBits_00001111) << 2 | (theInPtr[theInIndex + 2] & kBits_11000000) >> 6]; + outOutputData[theOutIndex++] = kBase64EncodeTable[(theInPtr[theInIndex + 2] & kBits_00111111) >> 0]; + if (theOutIndex % 74 == 72) + { + outOutputData[theOutIndex++] = '\r'; + outOutputData[theOutIndex++] = '\n'; + } + } +const size_t theRemainingBytes = inInputDataSize - theInIndex; +if (theRemainingBytes == 1) + { + outOutputData[theOutIndex++] = kBase64EncodeTable[(theInPtr[theInIndex] & kBits_11111100) >> 2]; + outOutputData[theOutIndex++] = kBase64EncodeTable[(theInPtr[theInIndex] & kBits_00000011) << 4 | (0 & kBits_11110000) >> 4]; + outOutputData[theOutIndex++] = '='; + outOutputData[theOutIndex++] = '='; + if (theOutIndex % 74 == 72) + { + outOutputData[theOutIndex++] = '\r'; + outOutputData[theOutIndex] = '\n'; + } + } +else if (theRemainingBytes == 2) + { + outOutputData[theOutIndex++] = kBase64EncodeTable[(theInPtr[theInIndex] & kBits_11111100) >> 2]; + outOutputData[theOutIndex++] = kBase64EncodeTable[(theInPtr[theInIndex] & kBits_00000011) << 4 | (theInPtr[theInIndex + 1] & kBits_11110000) >> 4]; + outOutputData[theOutIndex++] = kBase64EncodeTable[(theInPtr[theInIndex + 1] & kBits_00001111) << 2 | (0 & kBits_11000000) >> 6]; + outOutputData[theOutIndex++] = '='; + if (theOutIndex % 74 == 72) + { + outOutputData[theOutIndex++] = '\r'; + outOutputData[theOutIndex] = '\n'; + } + } +return(true); +} + +bool Base64DecodeData(const void *inInputData, size_t inInputDataSize, void *ioOutputData, size_t *ioOutputDataSize) +{ +memset(ioOutputData, '.', *ioOutputDataSize); + +size_t theDecodedDataSize = EstimateBas64DecodedDataSize(inInputDataSize); +if (*ioOutputDataSize < theDecodedDataSize) + return(false); +*ioOutputDataSize = 0; +const u_int8_t *theInPtr = (const u_int8_t *)inInputData; +u_int8_t *theOutPtr = (u_int8_t *)ioOutputData; +size_t theInIndex = 0, theOutIndex = 0; +u_int8_t theOutputOctet; +size_t theSequence = 0; +for (; theInIndex < inInputDataSize; ) + { + int8_t theSextet = 0; + + int8_t theCurrentInputOctet = theInPtr[theInIndex]; + theSextet = kBase64DecodeTable[theCurrentInputOctet]; + if (theSextet == -1) + break; + while (theSextet == -2) + { + theCurrentInputOctet = theInPtr[++theInIndex]; + theSextet = kBase64DecodeTable[theCurrentInputOctet]; + } + while (theSextet == -3) + { + theCurrentInputOctet = theInPtr[++theInIndex]; + theSextet = kBase64DecodeTable[theCurrentInputOctet]; + } + if (theSequence == 0) + { + theOutputOctet = (theSextet >= 0 ? theSextet : 0) << 2 & kBits_11111100; + } + else if (theSequence == 1) + { + theOutputOctet |= (theSextet >- 0 ? theSextet : 0) >> 4 & kBits_00000011; + theOutPtr[theOutIndex++] = theOutputOctet; + } + else if (theSequence == 2) + { + theOutputOctet = (theSextet >= 0 ? theSextet : 0) << 4 & kBits_11110000; + } + else if (theSequence == 3) + { + theOutputOctet |= (theSextet >= 0 ? theSextet : 0) >> 2 & kBits_00001111; + theOutPtr[theOutIndex++] = theOutputOctet; + } + else if (theSequence == 4) + { + theOutputOctet = (theSextet >= 0 ? theSextet : 0) << 6 & kBits_11000000; + } + else if (theSequence == 5) + { + theOutputOctet |= (theSextet >= 0 ? theSextet : 0) >> 0 & kBits_00111111; + theOutPtr[theOutIndex++] = theOutputOctet; + } + theSequence = (theSequence + 1) % 6; + if (theSequence != 2 && theSequence != 4) + theInIndex++; + } +*ioOutputDataSize = theOutIndex; +return(true); +} diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/Crypto/Base64Transcoder.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/Crypto/Base64Transcoder.h new file mode 100644 index 0000000..8752098 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/Crypto/Base64Transcoder.h @@ -0,0 +1,36 @@ +/* + * Base64Transcoder.h + * Base64Test + * + * Created by Jonathan Wight on Tue Mar 18 2003. + * Copyright (c) 2003 Toxic Software. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include +#include + +extern size_t EstimateBas64EncodedDataSize(size_t inDataSize); +extern size_t EstimateBas64DecodedDataSize(size_t inDataSize); + +extern bool Base64EncodeData(const void *inInputData, size_t inInputDataSize, char *outOutputData, size_t *ioOutputDataSize); +extern bool Base64DecodeData(const void *inInputData, size_t inInputDataSize, void *ioOutputData, size_t *ioOutputDataSize); + diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPDebug.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPDebug.h new file mode 100644 index 0000000..b59cd60 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPDebug.h @@ -0,0 +1,13 @@ +// +// MPDebug.h +// MPOAuthConnection +// +// Created by Karl Adam on 09.02.06. +// Copyright 2009 matrixPointer. All rights reserved. +// + +#ifdef DEBUG + #define MPLog(...) NSLog(__VA_ARGS__) +#else + #define MPLog(...) do { } while (0) +#endif diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuth.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuth.h new file mode 100644 index 0000000..062fced --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuth.h @@ -0,0 +1,20 @@ +// +// MPOAuth.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.13. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import + + +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPI.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPI.h new file mode 100644 index 0000000..1449322 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPI.h @@ -0,0 +1,86 @@ +// +// MPOAuthAPI.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import +#import "MPOAuthCredentialStore.h" +#import "MPOAuthParameterFactory.h" + +extern NSString * const MPOAuthNotificationAccessTokenReceived; +extern NSString * const MPOAuthNotificationAccessTokenRejected; +extern NSString * const MPOAuthNotificationAccessTokenRefreshed; +extern NSString * const MPOAuthNotificationOAuthCredentialsReady; +extern NSString * const MPOAuthNotificationErrorHasOccurred; + +extern NSString * const MPOAuthCredentialRequestTokenKey; +extern NSString * const MPOAuthCredentialRequestTokenSecretKey; +extern NSString * const MPOAuthCredentialAccessTokenKey; +extern NSString * const MPOAuthCredentialAccessTokenSecretKey; +extern NSString * const MPOAuthCredentialSessionHandleKey; + +extern NSString * const MPOAuthTokenRefreshDateDefaultsKey; + +typedef enum { + MPOAuthSignatureSchemePlainText, + MPOAuthSignatureSchemeHMACSHA1, + MPOAuthSignatureSchemeRSASHA1 +} MPOAuthSignatureScheme; + +typedef enum { + MPOAuthAuthenticationStateUnauthenticated = 0, + MPOAuthAuthenticationStateAuthenticating = 1, + MPOAuthAuthenticationStateAuthenticated = 2 +} MPOAuthAuthenticationState; + +@protocol MPOAuthAPIInternalClient +@end + +@class MPOAuthAuthenticationMethod; + +@interface MPOAuthAPI : NSObject { +@private + id credentials_; + NSURL *baseURL_; + NSURL *authenticationURL_; + MPOAuthAuthenticationMethod *authenticationMethod_; + MPOAuthSignatureScheme signatureScheme_; + NSMutableArray *activeLoaders_; + MPOAuthAuthenticationState oauthAuthenticationState_; +} + +@property (nonatomic, readonly, retain) id credentials; +@property (nonatomic, readonly, retain) NSURL *baseURL; +@property (nonatomic, readonly, retain) NSURL *authenticationURL; +@property (nonatomic, readwrite, retain) MPOAuthAuthenticationMethod *authenticationMethod; +@property (nonatomic, readwrite, assign) MPOAuthSignatureScheme signatureScheme; + +@property (nonatomic, readonly, assign) MPOAuthAuthenticationState authenticationState; + + +- (id)initWithCredentials:(NSDictionary *)inCredentials andBaseURL:(NSURL *)inURL; +- (id)initWithCredentials:(NSDictionary *)inCredentials authenticationURL:(NSURL *)inAuthURL andBaseURL:(NSURL *)inBaseURL; +- (id)initWithCredentials:(NSDictionary *)inCredentials authenticationURL:(NSURL *)inAuthURL andBaseURL:(NSURL *)inBaseURL autoStart:(BOOL)aFlag; + +- (void)authenticate; +- (BOOL)isAuthenticated; + +- (void)performMethod:(NSString *)inMethod withTarget:(id)inTarget andAction:(SEL)inAction; +- (void)performMethod:(NSString *)inMethod atURL:(NSURL *)inURL withParameters:(NSArray *)inParameters withTarget:(id)inTarget andAction:(SEL)inAction; +- (void)performPOSTMethod:(NSString *)inMethod atURL:(NSURL *)inURL withParameters:(NSArray *)inParameters withTarget:(id)inTarget andAction:(SEL)inAction; +- (void)performURLRequest:(NSURLRequest *)inRequest withTarget:(id)inTarget andAction:(SEL)inAction; + +- (NSData *)dataForMethod:(NSString *)inMethod; +- (NSData *)dataForMethod:(NSString *)inMethod withParameters:(NSArray *)inParameters; +- (NSData *)dataForURL:(NSURL *)inURL andMethod:(NSString *)inMethod withParameters:(NSArray *)inParameters; + +- (id)credentialNamed:(NSString *)inCredentialName; +- (void)setCredential:(id)inCredential withName:(NSString *)inName; +- (void)removeCredentialNamed:(NSString *)inName; + +- (void)discardCredentials; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPI.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPI.m new file mode 100644 index 0000000..e87fc80 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPI.m @@ -0,0 +1,224 @@ +// +// MPOAuthAPI.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "MPOAuthAPIRequestLoader.h" +#import "MPOAuthAPI.h" +#import "MPOAuthCredentialConcreteStore.h" +#import "MPOAuthURLRequest.h" +#import "MPOAuthURLResponse.h" +#import "MPURLRequestParameter.h" +#import "MPOAuthAuthenticationMethod.h" + +#import "NSURL+MPURLParameterAdditions.h" + +NSString *kMPOAuthCredentialConsumerKey = @"kMPOAuthCredentialConsumerKey"; +NSString *kMPOAuthCredentialConsumerSecret = @"kMPOAuthCredentialConsumerSecret"; +NSString *kMPOAuthCredentialUsername = @"kMPOAuthCredentialUsername"; +NSString *kMPOAuthCredentialPassword = @"kMPOAuthCredentialPassword"; +NSString *kMPOAuthCredentialRequestToken = @"kMPOAuthCredentialRequestToken"; +NSString *kMPOAuthCredentialRequestTokenSecret = @"kMPOAuthCredentialRequestTokenSecret"; +NSString *kMPOAuthCredentialAccessToken = @"kMPOAuthCredentialAccessToken"; +NSString *kMPOAuthCredentialAccessTokenSecret = @"kMPOAuthCredentialAccessTokenSecret"; +NSString *kMPOAuthCredentialSessionHandle = @"kMPOAuthCredentialSessionHandle"; + +NSString *kMPOAuthSignatureMethod = @"kMPOAuthSignatureMethod"; +NSString * const MPOAuthTokenRefreshDateDefaultsKey = @"MPOAuthAutomaticTokenRefreshLastExpiryDate"; + +@interface MPOAuthAPI () +@property (nonatomic, readwrite, retain) id credentials; +@property (nonatomic, readwrite, retain) NSURL *authenticationURL; +@property (nonatomic, readwrite, retain) NSURL *baseURL; +@property (nonatomic, readwrite, retain) NSMutableArray *activeLoaders; +@property (nonatomic, readwrite, assign) MPOAuthAuthenticationState authenticationState; + +- (void)performMethod:(NSString *)inMethod atURL:(NSURL *)inURL withParameters:(NSArray *)inParameters withTarget:(id)inTarget andAction:(SEL)inAction usingHTTPMethod:(NSString *)inHTTPMethod; +@end + +@implementation MPOAuthAPI + +- (id)initWithCredentials:(NSDictionary *)inCredentials andBaseURL:(NSURL *)inBaseURL { + return [self initWithCredentials:inCredentials authenticationURL:inBaseURL andBaseURL:inBaseURL]; +} + +- (id)initWithCredentials:(NSDictionary *)inCredentials authenticationURL:(NSURL *)inAuthURL andBaseURL:(NSURL *)inBaseURL { + return [self initWithCredentials:inCredentials authenticationURL:inBaseURL andBaseURL:inBaseURL autoStart:YES]; +} + +- (id)initWithCredentials:(NSDictionary *)inCredentials authenticationURL:(NSURL *)inAuthURL andBaseURL:(NSURL *)inBaseURL autoStart:(BOOL)aFlag { + if (self = [super init]) { + self.authenticationURL = inAuthURL; + self.baseURL = inBaseURL; + self.authenticationState = MPOAuthAuthenticationStateUnauthenticated; + credentials_ = [[MPOAuthCredentialConcreteStore alloc] initWithCredentials:inCredentials forBaseURL:inBaseURL withAuthenticationURL:inAuthURL]; + self.authenticationMethod = [[MPOAuthAuthenticationMethod alloc] initWithAPI:self forURL:inAuthURL]; + self.signatureScheme = MPOAuthSignatureSchemeHMACSHA1; + + activeLoaders_ = [[NSMutableArray alloc] initWithCapacity:10]; + + if (aFlag) { + [self authenticate]; + } + } + return self; +} + +- (oneway void)dealloc { + self.credentials = nil; + self.baseURL = nil; + self.authenticationURL = nil; + self.authenticationMethod = nil; + self.activeLoaders = nil; + + [super dealloc]; +} + +@synthesize credentials = credentials_; +@synthesize baseURL = baseURL_; +@synthesize authenticationURL = authenticationURL_; +@synthesize authenticationMethod = authenticationMethod_; +@synthesize signatureScheme = signatureScheme_; +@synthesize activeLoaders = activeLoaders_; +@synthesize authenticationState = oauthAuthenticationState_; + +#pragma mark - + +- (void)setSignatureScheme:(MPOAuthSignatureScheme)inScheme { + signatureScheme_ = inScheme; + + NSString *methodString = @"HMAC-SHA1"; + + switch (signatureScheme_) { + case MPOAuthSignatureSchemePlainText: + methodString = @"PLAINTEXT"; + break; + case MPOAuthSignatureSchemeRSASHA1: + methodString = @"RSA-SHA1"; + case MPOAuthSignatureSchemeHMACSHA1: + default: + // already initted to the default + break; + } + + [(MPOAuthCredentialConcreteStore *)credentials_ setSignatureMethod:methodString]; +} + +#pragma mark - + +- (void)authenticate { + NSAssert(credentials_.consumerKey, @"A Consumer Key is required for use of OAuth."); + [self.authenticationMethod authenticate]; +} + +- (BOOL)isAuthenticated { + return (self.authenticationState == MPOAuthAuthenticationStateAuthenticated); +} + +#pragma mark - + +- (void)performMethod:(NSString *)inMethod withTarget:(id)inTarget andAction:(SEL)inAction { + [self performMethod:inMethod atURL:self.baseURL withParameters:nil withTarget:inTarget andAction:inAction usingHTTPMethod:@"GET"]; +} + +- (void)performMethod:(NSString *)inMethod atURL:(NSURL *)inURL withParameters:(NSArray *)inParameters withTarget:(id)inTarget andAction:(SEL)inAction { + [self performMethod:inMethod atURL:inURL withParameters:inParameters withTarget:inTarget andAction:inAction usingHTTPMethod:@"GET"]; +} + +- (void)performPOSTMethod:(NSString *)inMethod atURL:(NSURL *)inURL withParameters:(NSArray *)inParameters withTarget:(id)inTarget andAction:(SEL)inAction { + [self performMethod:inMethod atURL:inURL withParameters:inParameters withTarget:inTarget andAction:inAction usingHTTPMethod:@"POST"]; +} + +- (void)performMethod:(NSString *)inMethod atURL:(NSURL *)inURL withParameters:(NSArray *)inParameters withTarget:(id)inTarget andAction:(SEL)inAction usingHTTPMethod:(NSString *)inHTTPMethod { + if (!inMethod && ![inURL path] && ![inURL query]) { + [NSException raise:@"MPOAuthNilMethodRequestException" format:@"Nil was passed as the method to be performed on %@", inURL]; + } + + NSURL *requestURL = inMethod ? [NSURL URLWithString:inMethod relativeToURL:inURL] : inURL; + MPOAuthURLRequest *aRequest = [[MPOAuthURLRequest alloc] initWithURL:requestURL andParameters:inParameters]; + MPOAuthAPIRequestLoader *loader = [[MPOAuthAPIRequestLoader alloc] initWithRequest:aRequest]; + + aRequest.HTTPMethod = inHTTPMethod; + loader.credentials = self.credentials; + loader.target = inTarget; + loader.action = inAction ? inAction : @selector(_performedLoad:receivingData:); + + [loader loadSynchronously:NO]; + // [self.activeLoaders addObject:loader]; + + [loader release]; + [aRequest release]; +} + +- (void)performURLRequest:(NSURLRequest *)inRequest withTarget:(id)inTarget andAction:(SEL)inAction { + if (!inRequest && ![[inRequest URL] path] && ![[inRequest URL] query]) { + [NSException raise:@"MPOAuthNilMethodRequestException" format:@"Nil was passed as the method to be performed on %@", inRequest]; + } + + MPOAuthURLRequest *aRequest = [[MPOAuthURLRequest alloc] initWithURLRequest:inRequest]; + MPOAuthAPIRequestLoader *loader = [[MPOAuthAPIRequestLoader alloc] initWithRequest:aRequest]; + + loader.credentials = self.credentials; + loader.target = inTarget; + loader.action = inAction ? inAction : @selector(_performedLoad:receivingData:); + + [loader loadSynchronously:NO]; + // [self.activeLoaders addObject:loader]; + + [loader release]; + [aRequest release]; +} + +- (NSData *)dataForMethod:(NSString *)inMethod { + return [self dataForURL:self.baseURL andMethod:inMethod withParameters:nil]; +} + +- (NSData *)dataForMethod:(NSString *)inMethod withParameters:(NSArray *)inParameters { + return [self dataForURL:self.baseURL andMethod:inMethod withParameters:inParameters]; +} + +- (NSData *)dataForURL:(NSURL *)inURL andMethod:(NSString *)inMethod withParameters:(NSArray *)inParameters { + NSURL *requestURL = [NSURL URLWithString:inMethod relativeToURL:inURL]; + MPOAuthURLRequest *aRequest = [[MPOAuthURLRequest alloc] initWithURL:requestURL andParameters:inParameters]; + MPOAuthAPIRequestLoader *loader = [[MPOAuthAPIRequestLoader alloc] initWithRequest:aRequest]; + + loader.credentials = self.credentials; + [loader loadSynchronously:YES]; + + [loader autorelease]; + [aRequest release]; + + return loader.data; +} + +#pragma mark - + +- (id)credentialNamed:(NSString *)inCredentialName { + return [self.credentials credentialNamed:inCredentialName]; +} + +- (void)setCredential:(id)inCredential withName:(NSString *)inName { + [(MPOAuthCredentialConcreteStore *)self.credentials setCredential:inCredential withName:inName]; +} + +- (void)removeCredentialNamed:(NSString *)inName { + [(MPOAuthCredentialConcreteStore *)self.credentials removeCredentialNamed:inName]; +} + +- (void)discardCredentials { + [self.credentials discardOAuthCredentials]; + + self.authenticationState = MPOAuthAuthenticationStateUnauthenticated; +} + +#pragma mark - +#pragma mark - Private APIs - + +- (void)_performedLoad:(MPOAuthAPIRequestLoader *)inLoader receivingData:(NSData *)inData { +// NSLog(@"loaded %@, and got %@", inLoader, inData); +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPIRequestLoader.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPIRequestLoader.h new file mode 100644 index 0000000..affe1a2 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPIRequestLoader.h @@ -0,0 +1,50 @@ +// +// MPOAuthAPIRequestLoader.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import + +extern NSString * const MPOAuthNotificationRequestTokenReceived; +extern NSString * const MPOAuthNotificationRequestTokenRejected; +extern NSString * const MPOAuthNotificationAccessTokenReceived; +extern NSString * const MPOAuthNotificationAccessTokenRejected; +extern NSString * const MPOAuthNotificationAccessTokenRefreshed; +extern NSString * const MPOAuthNotificationErrorHasOccurred; + +@protocol MPOAuthCredentialStore; +@protocol MPOAuthParameterFactory; + +@class MPOAuthURLRequest; +@class MPOAuthURLResponse; +@class MPOAuthCredentialConcreteStore; + +@interface MPOAuthAPIRequestLoader : NSObject { + MPOAuthCredentialConcreteStore *_credentials; + MPOAuthURLRequest *_oauthRequest; + MPOAuthURLResponse *_oauthResponse; + NSMutableData *_dataBuffer; + NSString *_dataAsString; + NSError *_error; + id _target; + SEL _action; +} + +@property (nonatomic, readwrite, retain) id credentials; +@property (nonatomic, readwrite, retain) MPOAuthURLRequest *oauthRequest; +@property (nonatomic, readwrite, retain) MPOAuthURLResponse *oauthResponse; +@property (nonatomic, readonly, retain) NSData *data; +@property (nonatomic, readonly, retain) NSString *responseString; +@property (nonatomic, readwrite, assign) id target; +@property (nonatomic, readwrite, assign) SEL action; + +- (id)initWithURL:(NSURL *)inURL; +- (id)initWithRequest:(MPOAuthURLRequest *)inRequest; + +- (void)loadSynchronously:(BOOL)inSynchronous; + +@end + diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPIRequestLoader.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPIRequestLoader.m new file mode 100644 index 0000000..e72f6df --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAPIRequestLoader.m @@ -0,0 +1,220 @@ +// +// MPOAuthAPIRequestLoader.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "MPOAuthAPIRequestLoader.h" +#import "MPOAuthURLRequest.h" +#import "MPOAuthURLResponse.h" +#import "MPOAuthConnection.h" +#import "MPOAuthCredentialStore.h" +#import "MPOAuthCredentialConcreteStore.h" +#import "MPURLRequestParameter.h" +#import "NSURLResponse+Encoding.h" +#import "MPDebug.h" + +NSString * const MPOAuthNotificationRequestTokenReceived = @"MPOAuthNotificationRequestTokenReceived"; +NSString * const MPOAuthNotificationRequestTokenRejected = @"MPOAuthNotificationRequestTokenRejected"; +NSString * const MPOAuthNotificationAccessTokenReceived = @"MPOAuthNotificationAccessTokenReceived"; +NSString * const MPOAuthNotificationAccessTokenRejected = @"MPOAuthNotificationAccessTokenRejected"; +NSString * const MPOAuthNotificationAccessTokenRefreshed = @"MPOAuthNotificationAccessTokenRefreshed"; +NSString * const MPOAuthNotificationOAuthCredentialsReady = @"MPOAuthNotificationOAuthCredentialsReady"; +NSString * const MPOAuthNotificationErrorHasOccurred = @"MPOAuthNotificationErrorHasOccurred"; + +@interface MPOAuthURLResponse () +@property (nonatomic, readwrite, retain) NSURLResponse *urlResponse; +@property (nonatomic, readwrite, retain) NSDictionary *oauthParameters; +@end + + +@interface MPOAuthAPIRequestLoader () +@property (nonatomic, readwrite, retain) NSData *data; +@property (nonatomic, readwrite, retain) NSString *responseString; + +- (void)_interrogateResponseForOAuthData; +@end + +@protocol MPOAuthAPIInternalClient; + +@implementation MPOAuthAPIRequestLoader + +- (id)initWithURL:(NSURL *)inURL { + return [self initWithRequest:[[[MPOAuthURLRequest alloc] initWithURL:inURL andParameters:nil] autorelease]]; +} + +- (id)initWithRequest:(MPOAuthURLRequest *)inRequest { + if (self = [super init]) { + self.oauthRequest = inRequest; + _dataBuffer = [[NSMutableData alloc] init]; + } + return self; +} + +- (oneway void)dealloc { + self.credentials = nil; + self.oauthRequest = nil; + self.oauthResponse = nil; + self.data = nil; + self.responseString = nil; + + [super dealloc]; +} + +@synthesize credentials = _credentials; +@synthesize oauthRequest = _oauthRequest; +@synthesize oauthResponse = _oauthResponse; +@synthesize data = _dataBuffer; +@synthesize responseString = _dataAsString; +@synthesize target = _target; +@synthesize action = _action; + +#pragma mark - + +- (MPOAuthURLResponse *)oauthResponse { + if (!_oauthResponse) { + _oauthResponse = [[MPOAuthURLResponse alloc] init]; + } + + return _oauthResponse; +} + +- (NSString *)responseString { + if (!_dataAsString) { + _dataAsString = [[NSString alloc] initWithData:self.data encoding:[self.oauthResponse.urlResponse encoding]]; + } + + return _dataAsString; +} + +- (void)loadSynchronously:(BOOL)inSynchronous { + NSAssert(_credentials, @"Unable to load without valid credentials"); + NSAssert(_credentials.consumerKey, @"Unable to load, credentials contain no consumer key"); + + if (!inSynchronous) { + [MPOAuthConnection connectionWithRequest:self.oauthRequest delegate:self credentials:self.credentials]; + } else { + MPOAuthURLResponse *theOAuthResponse = nil; + self.data = [MPOAuthConnection sendSynchronousRequest:self.oauthRequest usingCredentials:self.credentials returningResponse:&theOAuthResponse error:nil]; + self.oauthResponse = theOAuthResponse; + [self _interrogateResponseForOAuthData]; + } +} + +#pragma mark - + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + MPLog(@"%p: [%@ %@] %@, %@", self, NSStringFromClass([self class]), NSStringFromSelector(_cmd), connection, error); + if ([_target respondsToSelector:@selector(loader:didFailWithError:)]) { + [_target performSelector: @selector(loader:didFailWithError:) withObject: self withObject: error]; + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + self.oauthResponse.urlResponse = response; +} + +- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + MPLog(@"%@", NSStringFromSelector(_cmd)); +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + [_dataBuffer appendData:data]; +} + +- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse { + MPLog( @"[%@ %@]: %@, %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), request, redirectResponse); + return request; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + [self _interrogateResponseForOAuthData]; + + if (_action) { + if ([_target conformsToProtocol:@protocol(MPOAuthAPIInternalClient)]) { + [_target performSelector:_action withObject:self withObject:self.data]; + } else { + [_target performSelector:_action withObject:self.oauthRequest.url withObject:self.responseString]; + } + } +} + +#pragma mark - + +- (void)_interrogateResponseForOAuthData { + NSString *response = self.responseString; + NSDictionary *foundParameters = nil; + NSInteger status = [(NSHTTPURLResponse *)[self.oauthResponse urlResponse] statusCode]; + + if ([response length] > 5 && [[response substringToIndex:5] isEqualToString:@"oauth"]) { + foundParameters = [MPURLRequestParameter parameterDictionaryFromString:response]; + self.oauthResponse.oauthParameters = foundParameters; + + if (status == 401 || ([response length] > 13 && [[response substringToIndex:13] isEqualToString:@"oauth_problem"])) { + NSString *aParameterValue = nil; + MPLog(@"oauthProblem = %@", foundParameters); + + if ([foundParameters count] && (aParameterValue = [foundParameters objectForKey:@"oauth_problem"])) { + if ([aParameterValue isEqualToString:@"token_rejected"]) { + if (self.credentials.requestToken && !self.credentials.accessToken) { + [_credentials setRequestToken:nil]; + [_credentials setRequestTokenSecret:nil]; + + [[NSNotificationCenter defaultCenter] postNotificationName:MPOAuthNotificationRequestTokenRejected + object:nil + userInfo:foundParameters]; + } else if (self.credentials.accessToken && !self.credentials.requestToken) { + // your access token may be invalid due to a number of reasons so it's up to the + // user to decide whether or not to remove them + [[NSNotificationCenter defaultCenter] postNotificationName:MPOAuthNotificationAccessTokenRejected + object:nil + userInfo:foundParameters]; + + } + } + + // something's messed up, so throw an error + [[NSNotificationCenter defaultCenter] postNotificationName:MPOAuthNotificationErrorHasOccurred + object:nil + userInfo:foundParameters]; + } + } else if ([response length] > 11 && [[response substringToIndex:11] isEqualToString:@"oauth_token"]) { + NSString *aParameterValue = nil; + MPLog(@"foundParameters = %@", foundParameters); + + if ([foundParameters count] && (aParameterValue = [foundParameters objectForKey:@"oauth_token"])) { + if (!self.credentials.requestToken && !self.credentials.accessToken) { + [_credentials setRequestToken:aParameterValue]; + [_credentials setRequestTokenSecret:[foundParameters objectForKey:@"oauth_token_secret"]]; + + [[NSNotificationCenter defaultCenter] postNotificationName:MPOAuthNotificationRequestTokenReceived + object:nil + userInfo:foundParameters]; + + } else if (!self.credentials.accessToken && self.credentials.requestToken) { + [_credentials setRequestToken:nil]; + [_credentials setRequestTokenSecret:nil]; + [_credentials setAccessToken:aParameterValue]; + [_credentials setAccessTokenSecret:[foundParameters objectForKey:@"oauth_token_secret"]]; + + [[NSNotificationCenter defaultCenter] postNotificationName:MPOAuthNotificationAccessTokenReceived + object:nil + userInfo:foundParameters]; + + } else if (self.credentials.accessToken && !self.credentials.requestToken) { + // replace the current token + [_credentials setAccessToken:aParameterValue]; + [_credentials setAccessTokenSecret:[foundParameters objectForKey:@"oauth_token_secret"]]; + + [[NSNotificationCenter defaultCenter] postNotificationName:MPOAuthNotificationAccessTokenRefreshed + object:nil + userInfo:foundParameters]; + } + } + } + } +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethod.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethod.h new file mode 100644 index 0000000..f3dbd39 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethod.h @@ -0,0 +1,30 @@ +// +// MPOAuthAuthenticationMethod.h +// MPOAuthConnection +// +// Created by Karl Adam on 09.12.19. +// Copyright 2009 matrixPointer. All rights reserved. +// + +#import + +extern NSString * const MPOAuthAccessTokenURLKey; + +@class MPOAuthAPI; + +@interface MPOAuthAuthenticationMethod : NSObject { + MPOAuthAPI *oauthAPI_; + NSURL *oauthGetAccessTokenURL_; + NSTimer *refreshTimer_; +} + +@property (nonatomic, readwrite, assign) MPOAuthAPI *oauthAPI; +@property (nonatomic, readwrite, retain) NSURL *oauthGetAccessTokenURL; + +- (id)initWithAPI:(MPOAuthAPI *)inAPI forURL:(NSURL *)inURL; +- (id)initWithAPI:(MPOAuthAPI *)inAPI forURL:(NSURL *)inURL withConfiguration:(NSDictionary *)inConfig; +- (void)authenticate; + +- (void)setTokenRefreshInterval:(NSTimeInterval)inTimeInterval; +- (void)refreshAccessToken; +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethod.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethod.m new file mode 100644 index 0000000..70d6749 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethod.m @@ -0,0 +1,131 @@ +// +// MPOAuthAuthenticationMethod.m +// MPOAuthConnection +// +// Created by Karl Adam on 09.12.19. +// Copyright 2009 matrixPointer. All rights reserved. +// + +#import "MPOAuthAuthenticationMethod.h" +#import "MPOAuthAuthenticationMethodOAuth.h" +#import "MPOAuthCredentialConcreteStore.h" +#import "MPURLRequestParameter.h" + +#import "NSURL+MPURLParameterAdditions.h" + +NSString * const MPOAuthAccessTokenURLKey = @"MPOAuthAccessTokenURL"; + +@interface MPOAuthAuthenticationMethod () +@property (nonatomic, readwrite, retain) NSTimer *refreshTimer; + ++ (Class)_authorizationMethodClassForURL:(NSURL *)inBaseURL withConfiguration:(NSDictionary **)outConfig; +- (id)initWithAPI:(MPOAuthAPI *)inAPI forURL:(NSURL *)inURL withConfiguration:(NSDictionary *)inConfig; +- (void)_automaticallyRefreshAccessToken:(NSTimer *)inTimer; +@end + +@implementation MPOAuthAuthenticationMethod +- (id)initWithAPI:(MPOAuthAPI *)inAPI forURL:(NSURL *)inURL { + return [self initWithAPI:inAPI forURL:inURL withConfiguration:nil]; +} + +- (id)initWithAPI:(MPOAuthAPI *)inAPI forURL:(NSURL *)inURL withConfiguration:(NSDictionary *)inConfig { + if ([[self class] isEqual:[MPOAuthAuthenticationMethod class]]) { + NSDictionary *configuration = nil; + Class methodClass = [[self class] _authorizationMethodClassForURL:inURL withConfiguration:&configuration]; + [self release]; + + self = [[methodClass alloc] initWithAPI:inAPI forURL:inURL withConfiguration:configuration]; + } else if (self = [super init]) { + self.oauthAPI = inAPI; + } + + return self; +} + +- (oneway void)dealloc { + self.oauthAPI = nil; + self.oauthGetAccessTokenURL = nil; + + [self.refreshTimer invalidate]; + self.refreshTimer = nil; + + [super dealloc]; +} + +@synthesize oauthAPI = oauthAPI_; +@synthesize oauthGetAccessTokenURL = oauthGetAccessTokenURL_; +@synthesize refreshTimer = refreshTimer_; + +#pragma mark - + ++ (Class)_authorizationMethodClassForURL:(NSURL *)inBaseURL withConfiguration:(NSDictionary **)outConfig { + Class methodClass = [MPOAuthAuthenticationMethodOAuth class]; + NSString *oauthConfigPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"oauthAutoConfig" ofType:@"plist"]; + NSDictionary *oauthConfigDictionary = [NSDictionary dictionaryWithContentsOfFile:oauthConfigPath]; + + for ( NSString *domainString in [oauthConfigDictionary keyEnumerator]) { + if ([inBaseURL domainMatches:domainString]) { + NSDictionary *oauthConfig = [oauthConfigDictionary objectForKey:domainString]; + + NSArray *requestedMethods = [oauthConfig objectForKey:@"MPOAuthAuthenticationPreferredMethods"]; + NSString *requestedMethod = nil; + for (requestedMethod in requestedMethods) { + Class requestedMethodClass = NSClassFromString(requestedMethod); + + if (requestedMethodClass) { + methodClass = requestedMethodClass; + } + break; + } + + if (requestedMethod) { + *outConfig = [oauthConfig objectForKey:requestedMethod]; + } else { + *outConfig = oauthConfig; + } + + break; + } + } + + return methodClass; +} + +#pragma mark - + +- (void)authenticate { + [NSException raise:@"Not Implemented" format:@"All subclasses of MPOAuthAuthenticationMethod are required to implement -authenticate"]; +} + +- (void)setTokenRefreshInterval:(NSTimeInterval)inTimeInterval { + if (!self.refreshTimer && inTimeInterval > 0.0) { + self.refreshTimer = [NSTimer scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(_automaticallyRefreshAccessToken:) userInfo:nil repeats:YES]; + } +} + +- (void)refreshAccessToken { + MPURLRequestParameter *sessionHandleParameter = nil; + MPOAuthCredentialConcreteStore *credentials = (MPOAuthCredentialConcreteStore *)[self.oauthAPI credentials]; + + if (credentials.sessionHandle) { + sessionHandleParameter = [[MPURLRequestParameter alloc] init]; + sessionHandleParameter.name = @"oauth_session_handle"; + sessionHandleParameter.value = credentials.sessionHandle; + } + + [self.oauthAPI performMethod:nil + atURL:self.oauthGetAccessTokenURL + withParameters:sessionHandleParameter ? [NSArray arrayWithObject:sessionHandleParameter] : nil + withTarget:nil + andAction:nil]; + + [sessionHandleParameter release]; +} + +#pragma mark - + +- (void)_automaticallyRefreshAccessToken:(NSTimer *)inTimer { + [self refreshAccessToken]; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethodOAuth.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethodOAuth.h new file mode 100644 index 0000000..93f94a2 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethodOAuth.h @@ -0,0 +1,44 @@ +// +// MPOAuthAuthenticationMethodOAuth.h +// MPOAuthConnection +// +// Created by Karl Adam on 09.12.19. +// Copyright 2009 matrixPointer. All rights reserved. +// + +#import +#import "MPOAuthAuthenticationMethod.h" +#import "MPOAuthAPI.h" +#import "MPOAuthAPIRequestLoader.h" + +extern NSString * const MPOAuthNotificationRequestTokenReceived; +extern NSString * const MPOAuthNotificationRequestTokenRejected; + +@protocol MPOAuthAuthenticationMethodOAuthDelegate; + +@interface MPOAuthAuthenticationMethodOAuth : MPOAuthAuthenticationMethod { + NSURL *oauthRequestTokenURL_; + NSURL *oauthAuthorizeTokenURL_; + BOOL oauth10aModeActive_; + + id delegate_; +} + +@property (nonatomic, readwrite, assign) id delegate; + +@property (nonatomic, readwrite, retain) NSURL *oauthRequestTokenURL; +@property (nonatomic, readwrite, retain) NSURL *oauthAuthorizeTokenURL; + +- (void)authenticate; + +@end + +@protocol MPOAuthAuthenticationMethodOAuthDelegate +- (NSURL *)callbackURLForCompletedUserAuthorization; +- (BOOL)automaticallyRequestAuthenticationFromURL:(NSURL *)inAuthURL withCallbackURL:(NSURL *)inCallbackURL; + +@optional +- (NSString *)oauthVerifierForCompletedUserAuthorization; +- (void)authenticationDidFailWithError:(NSError *)error; +@end + diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethodOAuth.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethodOAuth.m new file mode 100644 index 0000000..7658b91 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthAuthenticationMethodOAuth.m @@ -0,0 +1,217 @@ +// +// MPOAuthAuthenticationMethodOAuth.m +// MPOAuthConnection +// +// Created by Karl Adam on 09.12.19. +// Copyright 2009 matrixPointer. All rights reserved. +// + +#import "MPOAuthAuthenticationMethodOAuth.h" +#import "MPOAuthAPI.h" +#import "MPOAuthAPIRequestLoader.h" +#import "MPOAuthURLResponse.h" +#import "MPOAuthCredentialStore.h" +#import "MPOAuthCredentialConcreteStore.h" +#import "MPDebug.h" +#import "MPURLRequestParameter.h" + +#import "NSURL+MPURLParameterAdditions.h" + +NSString *MPOAuthRequestTokenURLKey = @"MPOAuthRequestTokenURL"; +NSString *MPOAuthUserAuthorizationURLKey = @"MPOAuthUserAuthorizationURL"; +NSString *MPOAuthUserAuthorizationMobileURLKey = @"MPOAuthUserAuthorizationMobileURL"; + +NSString * const MPOAuthCredentialRequestTokenKey = @"oauth_token_request"; +NSString * const MPOAuthCredentialRequestTokenSecretKey = @"oauth_token_request_secret"; +NSString * const MPOAuthCredentialAccessTokenKey = @"oauth_token_access"; +NSString * const MPOAuthCredentialAccessTokenSecretKey = @"oauth_token_access_secret"; +NSString * const MPOAuthCredentialSessionHandleKey = @"oauth_session_handle"; +NSString * const MPOAuthCredentialVerifierKey = @"oauth_verifier"; + +@interface MPOAuthAPI () +@property (nonatomic, readwrite, assign) MPOAuthAuthenticationState authenticationState; +@end + + +@interface MPOAuthAuthenticationMethodOAuth () +@property (nonatomic, readwrite, assign) BOOL oauth10aModeActive; + +- (void)_authenticationRequestForRequestToken; +- (void)_authenticationRequestForUserPermissionsConfirmationAtURL:(NSURL *)inURL; +- (void)_authenticationRequestForAccessToken; + +@end + +@implementation MPOAuthAuthenticationMethodOAuth + +- (id)initWithAPI:(MPOAuthAPI *)inAPI forURL:(NSURL *)inURL withConfiguration:(NSDictionary *)inConfig { + if (self = [super initWithAPI:inAPI forURL:inURL withConfiguration:inConfig]) { + + NSAssert( [inConfig count] >= 3, @"Incorrect number of oauth authorization methods"); + self.oauthRequestTokenURL = [NSURL URLWithString:[inConfig objectForKey:MPOAuthRequestTokenURLKey]]; + self.oauthAuthorizeTokenURL = [NSURL URLWithString:[inConfig objectForKey:MPOAuthUserAuthorizationURLKey]]; + self.oauthGetAccessTokenURL = [NSURL URLWithString:[inConfig objectForKey:MPOAuthAccessTokenURLKey]]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_requestTokenReceived:) name:MPOAuthNotificationRequestTokenReceived object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_requestTokenRejected:) name:MPOAuthNotificationRequestTokenRejected object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_accessTokenReceived:) name:MPOAuthNotificationAccessTokenReceived object:nil]; + } + return self; +} + +- (oneway void)dealloc { + self.oauthRequestTokenURL = nil; + self.oauthAuthorizeTokenURL = nil; + + [super dealloc]; +} + +@synthesize delegate = delegate_; +@synthesize oauthRequestTokenURL = oauthRequestTokenURL_; +@synthesize oauthAuthorizeTokenURL = oauthAuthorizeTokenURL_; +@synthesize oauth10aModeActive = oauth10aModeActive_; + +#pragma mark - + +- (void)authenticate { + id credentials = [self.oauthAPI credentials]; + + if (!credentials.accessToken && !credentials.requestToken) { + [self _authenticationRequestForRequestToken]; + } else if (!credentials.accessToken) { + [self _authenticationRequestForAccessToken]; + } else if (credentials.accessToken && [[NSUserDefaults standardUserDefaults] objectForKey:MPOAuthTokenRefreshDateDefaultsKey]) { + NSTimeInterval expiryDateInterval = [[NSUserDefaults standardUserDefaults] doubleForKey:MPOAuthTokenRefreshDateDefaultsKey]; + NSDate *tokenExpiryDate = [NSDate dateWithTimeIntervalSinceReferenceDate:expiryDateInterval]; + + if ([tokenExpiryDate compare:[NSDate date]] == NSOrderedAscending) { + [self refreshAccessToken]; + } + } +} + +- (void)_authenticationRequestForRequestToken { + if (self.oauthRequestTokenURL) { + MPLog(@"--> Performing Request Token Request: %@", self.oauthRequestTokenURL); + + // Append the oauth_callbackUrl parameter for requesting the request token + MPURLRequestParameter *callbackParameter = nil; + if (self.delegate && [self.delegate respondsToSelector: @selector(callbackURLForCompletedUserAuthorization)]) { + NSURL *callbackURL = [self.delegate callbackURLForCompletedUserAuthorization]; + callbackParameter = [[[MPURLRequestParameter alloc] initWithName:@"oauth_callback" andValue:[callbackURL absoluteString]] autorelease]; + } else { + // oob = "Out of bounds" + callbackParameter = [[[MPURLRequestParameter alloc] initWithName:@"oauth_callback" andValue:@"oob"] autorelease]; + } + + NSArray *params = [NSArray arrayWithObject:callbackParameter]; + [self.oauthAPI performMethod:nil atURL:self.oauthRequestTokenURL withParameters:params withTarget:self andAction:@selector(_authenticationRequestForRequestTokenSuccessfulLoad:withData:)]; + } +} + +- (void)_authenticationRequestForRequestTokenSuccessfulLoad:(MPOAuthAPIRequestLoader *)inLoader withData:(NSData *)inData { + NSDictionary *oauthResponseParameters = inLoader.oauthResponse.oauthParameters; + NSString *xoauthRequestAuthURL = [oauthResponseParameters objectForKey:@"xoauth_request_auth_url"]; // a common custom extension, used by Yahoo! + NSURL *userAuthURL = xoauthRequestAuthURL ? [NSURL URLWithString:xoauthRequestAuthURL] : self.oauthAuthorizeTokenURL; + NSURL *callbackURL = nil; + + if (!self.oauth10aModeActive) { + callbackURL = [self.delegate respondsToSelector:@selector(callbackURLForCompletedUserAuthorization)] ? [self.delegate callbackURLForCompletedUserAuthorization] : nil; + } + + NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys: [oauthResponseParameters objectForKey: @"oauth_token"], @"oauth_token", + callbackURL, @"oauth_callback", + nil]; + + userAuthURL = [userAuthURL urlByAddingParameterDictionary:parameters]; + BOOL delegateWantsToBeInvolved = [self.delegate respondsToSelector:@selector(automaticallyRequestAuthenticationFromURL:withCallbackURL:)]; + + if (!delegateWantsToBeInvolved || (delegateWantsToBeInvolved && [self.delegate automaticallyRequestAuthenticationFromURL:userAuthURL withCallbackURL:callbackURL])) { + MPLog(@"--> Automatically Performing User Auth Request: %@", userAuthURL); + [self _authenticationRequestForUserPermissionsConfirmationAtURL:userAuthURL]; + } +} + +- (void)loader:(MPOAuthAPIRequestLoader *)inLoader didFailWithError:(NSError *)error { + if ([self.delegate respondsToSelector:@selector(authenticationDidFailWithError:)]) { + [self.delegate authenticationDidFailWithError: error]; + } +} + +- (void)_authenticationRequestForUserPermissionsConfirmationAtURL:(NSURL *)userAuthURL { +#if TARGET_OS_IPHONE + [[UIApplication sharedApplication] openURL:userAuthURL]; +#else + [[NSWorkspace sharedWorkspace] openURL:userAuthURL]; +#endif +} + +- (void)_authenticationRequestForAccessToken { + NSArray *params = nil; + + if (self.delegate && [self.delegate respondsToSelector: @selector(oauthVerifierForCompletedUserAuthorization)]) { + MPURLRequestParameter *verifierParameter = nil; + + NSString *verifier = [self.delegate oauthVerifierForCompletedUserAuthorization]; + if (verifier) { + verifierParameter = [[[MPURLRequestParameter alloc] initWithName:@"oauth_verifier" andValue:verifier] autorelease]; + params = [NSArray arrayWithObject:verifierParameter]; + } + } + + if (self.oauthGetAccessTokenURL) { + MPLog(@"--> Performing Access Token Request: %@", self.oauthGetAccessTokenURL); + [self.oauthAPI performMethod:nil atURL:self.oauthGetAccessTokenURL withParameters:params withTarget:self andAction:nil]; + } +} + +#pragma mark - + +- (void)_requestTokenReceived:(NSNotification *)inNotification { + if ([[inNotification userInfo] objectForKey:@"oauth_callback_confirmed"]) { + self.oauth10aModeActive = YES; + } + + [self.oauthAPI setCredential:[[inNotification userInfo] objectForKey:@"oauth_token"] withName:kMPOAuthCredentialRequestToken]; + [self.oauthAPI setCredential:[[inNotification userInfo] objectForKey:@"oauth_token_secret"] withName:kMPOAuthCredentialRequestTokenSecret]; +} + +- (void)_requestTokenRejected:(NSNotification *)inNotification { + [self.oauthAPI removeCredentialNamed:MPOAuthCredentialRequestTokenKey]; + [self.oauthAPI removeCredentialNamed:MPOAuthCredentialRequestTokenSecretKey]; +} + +- (void)_accessTokenReceived:(NSNotification *)inNotification { + [self.oauthAPI removeCredentialNamed:MPOAuthCredentialRequestTokenKey]; + [self.oauthAPI removeCredentialNamed:MPOAuthCredentialRequestTokenSecretKey]; + + [self.oauthAPI setCredential:[[inNotification userInfo] objectForKey:@"oauth_token"] withName:kMPOAuthCredentialAccessToken]; + [self.oauthAPI setCredential:[[inNotification userInfo] objectForKey:@"oauth_token_secret"] withName:kMPOAuthCredentialAccessTokenSecret]; + + if ([[inNotification userInfo] objectForKey:MPOAuthCredentialSessionHandleKey]) { + [self.oauthAPI setCredential:[[inNotification userInfo] objectForKey:MPOAuthCredentialSessionHandleKey] withName:kMPOAuthCredentialSessionHandle]; + } + + [self.oauthAPI setAuthenticationState:MPOAuthAuthenticationStateAuthenticated]; + + if ([[inNotification userInfo] objectForKey:@"oauth_expires_in"]) { + NSTimeInterval tokenRefreshInterval = (NSTimeInterval)[[[inNotification userInfo] objectForKey:@"oauth_expires_in"] intValue]; + NSDate *tokenExpiryDate = [NSDate dateWithTimeIntervalSinceNow:tokenRefreshInterval]; + [[NSUserDefaults standardUserDefaults] setDouble:[tokenExpiryDate timeIntervalSinceReferenceDate] forKey:MPOAuthTokenRefreshDateDefaultsKey]; + + if (tokenRefreshInterval > 0.0) { + [self setTokenRefreshInterval:tokenRefreshInterval]; + } + } else { + [[NSUserDefaults standardUserDefaults] removeObjectForKey:MPOAuthTokenRefreshDateDefaultsKey]; + } +} + +#pragma mark - +#pragma mark - Private APIs - + +- (void)_performedLoad:(MPOAuthAPIRequestLoader *)inLoader receivingData:(NSData *)inData { + // NSLog(@"loaded %@, and got %@", inLoader, inData); +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthConnection.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthConnection.h new file mode 100644 index 0000000..71db8da --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthConnection.h @@ -0,0 +1,29 @@ +// +// MPOAuthConnection.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import + +@protocol MPOAuthCredentialStore; +@protocol MPOAuthParameterFactory; + +@class MPOAuthURLRequest; +@class MPOAuthURLResponse; +@class MPOAuthCredentialConcreteStore; + +@interface MPOAuthConnection : NSURLConnection { +@private + MPOAuthCredentialConcreteStore *_credentials; +} + +@property (nonatomic, readonly) id credentials; + ++ (MPOAuthConnection *)connectionWithRequest:(MPOAuthURLRequest *)inRequest delegate:(id)inDelegate credentials:(NSObject *)inCredentials; ++ (NSData *)sendSynchronousRequest:(MPOAuthURLRequest *)inRequest usingCredentials:(NSObject *)inCredentials returningResponse:(MPOAuthURLResponse **)outResponse error:(NSError **)inError; +- (id)initWithRequest:(MPOAuthURLRequest *)inRequest delegate:(id)inDelegate credentials:(NSObject *)inCredentials; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthConnection.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthConnection.m new file mode 100644 index 0000000..3aeb0b3 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthConnection.m @@ -0,0 +1,58 @@ +// +// MPOAuthConnection.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "MPOAuthConnection.h" +#import "MPOAuthURLRequest.h" +#import "MPOAuthURLResponse.h" +#import "MPOAuthParameterFactory.h" +#import "MPOAuthCredentialConcreteStore.h" + +@interface MPOAuthURLResponse () +@property (nonatomic, readwrite, retain) NSURLResponse *urlResponse; +@property (nonatomic, readwrite, retain) NSDictionary *oauthParameters; +@end + +@implementation MPOAuthConnection + ++ (MPOAuthConnection *)connectionWithRequest:(MPOAuthURLRequest *)inRequest delegate:(id)inDelegate credentials:(NSObject *)inCredentials { + MPOAuthConnection *aConnection = [[MPOAuthConnection alloc] initWithRequest:inRequest delegate:inDelegate credentials:inCredentials]; + return [aConnection autorelease]; +} + ++ (NSData *)sendSynchronousRequest:(MPOAuthURLRequest *)inRequest usingCredentials:(NSObject *)inCredentials returningResponse:(MPOAuthURLResponse **)outResponse error:(NSError **)inError { + [inRequest addParameters:[inCredentials oauthParameters]]; + NSURLRequest *urlRequest = [inRequest urlRequestSignedWithSecret:[inCredentials signingKey] usingMethod:[inCredentials signatureMethod]]; + NSURLResponse *urlResponse = nil; + NSData *responseData = [self sendSynchronousRequest:urlRequest returningResponse:&urlResponse error:inError]; + MPOAuthURLResponse *oauthResponse = [[[MPOAuthURLResponse alloc] init] autorelease]; + oauthResponse.urlResponse = urlResponse; + *outResponse = oauthResponse; + + return responseData; +} + +- (id)initWithRequest:(MPOAuthURLRequest *)inRequest delegate:(id)inDelegate credentials:(NSObject *)inCredentials { + [inRequest addParameters:[inCredentials oauthParameters]]; + NSURLRequest *urlRequest = [inRequest urlRequestSignedWithSecret:[inCredentials signingKey] usingMethod:[inCredentials signatureMethod]]; + if (self = [super initWithRequest:urlRequest delegate:inDelegate]) { + _credentials = [inCredentials retain]; + } + return self; +} + +- (oneway void)dealloc { + [_credentials release]; + + [super dealloc]; +} + +@synthesize credentials = _credentials; + +#pragma mark - + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentiaIConcreteStore+KeychainAdditionsMac.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentiaIConcreteStore+KeychainAdditionsMac.m new file mode 100644 index 0000000..8d4ed18 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentiaIConcreteStore+KeychainAdditionsMac.m @@ -0,0 +1,89 @@ +// +// MPOAuthCredentialConcreteStore+TokenAdditionsMac.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.13. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "MPOAuthCredentialConcreteStore+KeychainAdditions.h" + +#if !TARGET_OS_IPHONE || (TARGET_IPHONE_SIMULATOR && !__IPHONE_3_0) + +@interface MPOAuthCredentialConcreteStore (KeychainAdditionsMac) +- (NSString *)findValueFromKeychainUsingName:(NSString *)inName returningItem:(SecKeychainItemRef *)outKeychainItemRef; +@end + +@implementation MPOAuthCredentialConcreteStore (KeychainAdditions) + +- (void)addToKeychainUsingName:(NSString *)inName andValue:(NSString *)inValue { + NSString *serverName = [self.baseURL host]; + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + NSString *securityDomain = [self.authenticationURL host]; + NSString *uniqueName = [NSString stringWithFormat:@"%@.%@", bundleID, inName]; + SecKeychainItemRef existingKeychainItem = NULL; + + if ([self findValueFromKeychainUsingName:inName returningItem:&existingKeychainItem]) { + // This is MUCH easier than updating the item attributes/data + SecKeychainItemDelete(existingKeychainItem); + } + + SecKeychainAddInternetPassword(NULL /* default keychain */, + [serverName length], [serverName UTF8String], + [securityDomain length], [securityDomain UTF8String], + [uniqueName length], [uniqueName UTF8String], /* account name */ + 0, NULL, /* path */ + 0, + 'oaut' /* OAuth, not an official OSType code */, + kSecAuthenticationTypeDefault, + [inValue length], [inValue UTF8String], + NULL); +} + +- (NSString *)findValueFromKeychainUsingName:(NSString *)inName { + return [self findValueFromKeychainUsingName:inName returningItem:NULL]; +} + +- (NSString *)findValueFromKeychainUsingName:(NSString *)inName returningItem:(SecKeychainItemRef *)outKeychainItemRef { + NSString *foundPassword = nil; + NSString *serverName = [self.baseURL host]; + NSString *securityDomain = [self.authenticationURL host]; + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + NSString *uniqueName = [NSString stringWithFormat:@"%@.%@", bundleID, inName]; + + UInt32 passwordLength = 0; + const char *passwordString = NULL; + + OSStatus status = SecKeychainFindInternetPassword(NULL /* default keychain */, + [serverName length], [serverName UTF8String], + [securityDomain length], [securityDomain UTF8String], + [uniqueName length], [uniqueName UTF8String], + 0, NULL, /* path */ + 0, + kSecProtocolTypeAny, + kSecAuthenticationTypeAny, + (UInt32 *)&passwordLength, + (void **)&passwordString, + outKeychainItemRef); + + if (status == noErr && passwordLength) { + NSData *passwordStringData = [NSData dataWithBytes:passwordString length:passwordLength]; + foundPassword = [[NSString alloc] initWithData:passwordStringData encoding:NSUTF8StringEncoding]; + } + + return [foundPassword autorelease]; +} + +- (void)removeValueFromKeychainUsingName:(NSString *)inName { + SecKeychainItemRef aKeychainItem = NULL; + + [self findValueFromKeychainUsingName:inName returningItem:&aKeychainItem]; + + if (aKeychainItem) { + SecKeychainItemDelete(aKeychainItem); + } +} + +@end + +#endif diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore+KeychainAdditions.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore+KeychainAdditions.h new file mode 100644 index 0000000..72d2c19 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore+KeychainAdditions.h @@ -0,0 +1,18 @@ +// +// MPOAuthCredentialConcreteStore+TokenAdditionsMac.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.13. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import +#import "MPOAuthCredentialConcreteStore.h" + +@interface MPOAuthCredentialConcreteStore (KeychainAdditions) + +- (void)addToKeychainUsingName:(NSString *)inName andValue:(NSString *)inValue; +- (NSString *)findValueFromKeychainUsingName:(NSString *)inName; +- (void)removeValueFromKeychainUsingName:(NSString *)inName; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore+KeychainAdditionsiPhone.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore+KeychainAdditionsiPhone.m new file mode 100644 index 0000000..f1f4c15 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore+KeychainAdditionsiPhone.m @@ -0,0 +1,112 @@ +// +// MPOAuthCredentialConcreteStore+TokenAdditionsiPhone.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.13. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "MPOAuthCredentialConcreteStore+KeychainAdditions.h" +#import + +#if TARGET_OS_IPHONE && (!TARGET_IPHONE_SIMULATOR || __IPHONE_3_0) + +@interface MPOAuthCredentialConcreteStore (TokenAdditionsiPhone) +- (NSString *)findValueFromKeychainUsingName:(NSString *)inName returningItem:(NSDictionary **)outKeychainItemRef; +@end + +@implementation MPOAuthCredentialConcreteStore (KeychainAdditions) + +- (void)addToKeychainUsingName:(NSString *)inName andValue:(NSString *)inValue { + NSString *serverName = [self.baseURL host]; + NSString *securityDomain = [self.authenticationURL host]; +// NSString *itemID = [NSString stringWithFormat:@"%@.oauth.%@", [[NSBundle mainBundle] bundleIdentifier], inName]; + NSDictionary *searchDictionary = nil; + NSDictionary *keychainItemAttributeDictionary = [NSDictionary dictionaryWithObjectsAndKeys: (id)kSecClassInternetPassword, kSecClass, + securityDomain, kSecAttrSecurityDomain, + serverName, kSecAttrServer, + inName, kSecAttrAccount, + kSecAttrAuthenticationTypeDefault, kSecAttrAuthenticationType, + [NSNumber numberWithUnsignedLongLong:'oaut'], kSecAttrType, + [inValue dataUsingEncoding:NSUTF8StringEncoding], kSecValueData, + nil]; + + + if ([self findValueFromKeychainUsingName:inName returningItem:&searchDictionary]) { + NSMutableDictionary *updateDictionary = [keychainItemAttributeDictionary mutableCopy]; + [updateDictionary removeObjectForKey:(id)kSecClass]; + + SecItemUpdate((CFDictionaryRef)keychainItemAttributeDictionary, (CFDictionaryRef)updateDictionary); + [updateDictionary release]; + } else { + OSStatus success = SecItemAdd( (CFDictionaryRef)keychainItemAttributeDictionary, NULL); + + if (success == errSecNotAvailable) { + [NSException raise:@"Keychain Not Available" format:@"Keychain Access Not Currently Available"]; + } else if (success == errSecDuplicateItem) { + [NSException raise:@"Keychain duplicate item exception" format:@"Item already exists for %@", keychainItemAttributeDictionary]; + } + } +} + +- (NSString *)findValueFromKeychainUsingName:(NSString *)inName { + return [self findValueFromKeychainUsingName:inName returningItem:NULL]; +} + +- (NSString *)findValueFromKeychainUsingName:(NSString *)inName returningItem:(NSDictionary **)outKeychainItemRef { + NSString *foundPassword = nil; + NSString *serverName = [self.baseURL host]; + NSString *securityDomain = [self.authenticationURL host]; + NSDictionary *attributesDictionary = nil; + NSData *foundValue = nil; + OSStatus status = noErr; +// NSString *itemID = [NSString stringWithFormat:@"%@.oauth.%@", [[NSBundle mainBundle] bundleIdentifier], inName]; + + NSMutableDictionary *searchDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:(id)kSecClassInternetPassword, (id)kSecClass, + securityDomain, (id)kSecAttrSecurityDomain, + serverName, (id)kSecAttrServer, + inName, (id)kSecAttrAccount, + (id)kSecMatchLimitOne, (id)kSecMatchLimit, + (id)kCFBooleanTrue, (id)kSecReturnData, + (id)kCFBooleanTrue, (id)kSecReturnAttributes, + (id)kCFBooleanTrue, (id)kSecReturnPersistentRef, + nil]; + + status = SecItemCopyMatching((CFDictionaryRef)searchDictionary, (CFTypeRef *)&attributesDictionary); + foundValue = [attributesDictionary objectForKey:(id)kSecValueData]; + if (outKeychainItemRef) { + *outKeychainItemRef = attributesDictionary; + } + + if (status == noErr && foundValue) { + foundPassword = [[NSString alloc] initWithData:foundValue encoding:NSUTF8StringEncoding]; + } + + return [foundPassword autorelease]; +} + +- (void)removeValueFromKeychainUsingName:(NSString *)inName { + NSString *serverName = [self.baseURL host]; + NSString *securityDomain = [self.authenticationURL host]; + + NSMutableDictionary *searchDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys: (id)kSecClassInternetPassword, (id)kSecClass, + securityDomain, (id)kSecAttrSecurityDomain, + serverName, (id)kSecAttrServer, + inName, (id)kSecAttrAccount, + nil]; + + OSStatus success = SecItemDelete((CFDictionaryRef)searchDictionary); + + if (success == errSecNotAvailable) { + [NSException raise:@"Keychain Not Available" format:@"Keychain Access Not Currently Available"]; + } else if (success == errSecParam) { + [NSException raise:@"Keychain parameter error" format:@"One or more parameters passed to the function were not valid from %@", searchDictionary]; + } else if (success == errSecAllocate) { + [NSException raise:@"Keychain memory error" format:@"Failed to allocate memory"]; + } + +} + +@end + +#endif TARGET_OS_IPHONE diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore.h new file mode 100644 index 0000000..9906415 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore.h @@ -0,0 +1,40 @@ +// +// MPOAuthCredentialConcreteStore.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.11. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import +#import "MPOAuthCredentialStore.h" +#import "MPOAuthParameterFactory.h" + +@interface MPOAuthCredentialConcreteStore : NSObject { + NSMutableDictionary *store_; + NSURL *baseURL_; + NSURL *authenticationURL_; +} + +@property (nonatomic, readonly, retain) NSURL *baseURL; +@property (nonatomic, readonly, retain) NSURL *authenticationURL; + +@property (nonatomic, readonly) NSString *tokenSecret; +@property (nonatomic, readonly) NSString *signingKey; + +@property (nonatomic, readwrite, retain) NSString *requestToken; +@property (nonatomic, readwrite, retain) NSString *requestTokenSecret; +@property (nonatomic, readwrite, retain) NSString *accessToken; +@property (nonatomic, readwrite, retain) NSString *accessTokenSecret; + +@property (nonatomic, readwrite, retain) NSString *sessionHandle; + +- (id)initWithCredentials:(NSDictionary *)inCredential; +- (id)initWithCredentials:(NSDictionary *)inCredentials forBaseURL:(NSURL *)inBaseURL; +- (id)initWithCredentials:(NSDictionary *)inCredentials forBaseURL:(NSURL *)inBaseURL withAuthenticationURL:(NSURL *)inAuthenticationURL; + +- (void)setCredential:(id)inCredential withName:(NSString *)inName; +- (void)removeCredentialNamed:(NSString *)inName; + + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore.m new file mode 100644 index 0000000..87e0caa --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialConcreteStore.m @@ -0,0 +1,284 @@ +// +// MPOAuthCredentialConcreteStore.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.11. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "MPOAuthCredentialConcreteStore.h" +#import "MPURLRequestParameter.h" + +#import "MPOAuthCredentialConcreteStore+KeychainAdditions.h" +#import "NSString+URLEscapingAdditions.h" + +extern NSString * const MPOAuthCredentialRequestTokenKey; +extern NSString * const MPOAuthCredentialRequestTokenSecretKey; +extern NSString * const MPOAuthCredentialAccessTokenKey; +extern NSString * const MPOAuthCredentialAccessTokenSecretKey; +extern NSString * const MPOAuthCredentialSessionHandleKey; + +@interface MPOAuthCredentialConcreteStore () +@property (nonatomic, readwrite, retain) NSMutableDictionary *store; +@property (nonatomic, readwrite, retain) NSURL *baseURL; +@property (nonatomic, readwrite, retain) NSURL *authenticationURL; +@end + +@implementation MPOAuthCredentialConcreteStore + +- (id)initWithCredentials:(NSDictionary *)inCredentials { + return [self initWithCredentials:inCredentials forBaseURL:nil]; +} + +- (id)initWithCredentials:(NSDictionary *)inCredentials forBaseURL:(NSURL *)inBaseURL { + return [self initWithCredentials:inCredentials forBaseURL:inBaseURL withAuthenticationURL:inBaseURL]; +} + +- (id)initWithCredentials:(NSDictionary *)inCredentials forBaseURL:(NSURL *)inBaseURL withAuthenticationURL:(NSURL *)inAuthenticationURL { + if (self = [super init]) { + store_ = [[NSMutableDictionary alloc] initWithDictionary:inCredentials]; + self.baseURL = inBaseURL; + self.authenticationURL = inAuthenticationURL; + } + return self; +} + +- (oneway void)dealloc { + self.store = nil; + self.baseURL = nil; + self.authenticationURL = nil; + + [super dealloc]; +} + +@synthesize store = store_; +@synthesize baseURL = baseURL_; +@synthesize authenticationURL = authenticationURL_; + +#pragma mark - + +- (NSString *)consumerKey { + return [self.store objectForKey:kMPOAuthCredentialConsumerKey]; +} + +- (NSString *)consumerSecret { + return [self.store objectForKey:kMPOAuthCredentialConsumerSecret]; +} + +- (NSString *)username { + return [self.store objectForKey:kMPOAuthCredentialUsername]; +} + +- (NSString *)password { + return [self.store objectForKey:kMPOAuthCredentialPassword]; +} + +- (NSString *)requestToken { + return [self.store objectForKey:kMPOAuthCredentialRequestToken]; +} + +- (void)setRequestToken:(NSString *)inToken { + if (inToken) { + [self.store setObject:inToken forKey:kMPOAuthCredentialRequestToken]; + } else { + [self.store removeObjectForKey:kMPOAuthCredentialRequestToken]; + [self removeValueFromKeychainUsingName:kMPOAuthCredentialRequestToken]; + } +} + +- (NSString *)requestTokenSecret { + return [self.store objectForKey:kMPOAuthCredentialRequestTokenSecret]; +} + +- (void)setRequestTokenSecret:(NSString *)inTokenSecret { + if (inTokenSecret) { + [self.store setObject:inTokenSecret forKey:kMPOAuthCredentialRequestTokenSecret]; + } else { + [self.store removeObjectForKey:kMPOAuthCredentialRequestTokenSecret]; + [self removeValueFromKeychainUsingName:kMPOAuthCredentialRequestTokenSecret]; + } +} + +- (NSString *)accessToken { + return [self.store objectForKey:kMPOAuthCredentialAccessToken]; +} + +- (void)setAccessToken:(NSString *)inToken { + if (inToken) { + [self.store setObject:inToken forKey:kMPOAuthCredentialAccessToken]; + } else { + [self.store removeObjectForKey:kMPOAuthCredentialAccessToken]; + [self removeValueFromKeychainUsingName:kMPOAuthCredentialAccessToken]; + } +} + +- (NSString *)accessTokenSecret { + return [self.store objectForKey:kMPOAuthCredentialAccessTokenSecret]; +} + +- (void)setAccessTokenSecret:(NSString *)inTokenSecret { + if (inTokenSecret) { + [self.store setObject:inTokenSecret forKey:kMPOAuthCredentialAccessTokenSecret]; + } else { + [self.store removeObjectForKey:kMPOAuthCredentialAccessTokenSecret]; + [self removeValueFromKeychainUsingName:kMPOAuthCredentialAccessTokenSecret]; + } +} + +- (NSString *)sessionHandle { + return [self.store objectForKey:kMPOAuthCredentialSessionHandle]; +} + +- (void)setSessionHandle:(NSString *)inSessionHandle { + if (inSessionHandle) { + [self.store setObject:inSessionHandle forKey:kMPOAuthCredentialSessionHandle]; + } else { + [self.store removeObjectForKey:kMPOAuthCredentialSessionHandle]; + [self removeValueFromKeychainUsingName:kMPOAuthCredentialSessionHandle]; + } +} + +#pragma mark - + +- (NSString *)credentialNamed:(NSString *)inCredentialName { + return [store_ objectForKey:inCredentialName]; +} + +- (void)setCredential:(id)inCredential withName:(NSString *)inName { + [self.store setObject:inCredential forKey:inName]; + [self addToKeychainUsingName:inName andValue:inCredential]; +} + +- (void)removeCredentialNamed:(NSString *)inName { + [self.store removeObjectForKey:inName]; + [self removeValueFromKeychainUsingName:inName]; +} + +- (void)discardOAuthCredentials { + self.requestToken = nil; + self.requestTokenSecret = nil; + self.accessToken = nil; + self.accessTokenSecret = nil; + self.sessionHandle = nil; +} + +#pragma mark - + +- (NSString *)tokenSecret { + NSString *tokenSecret = @""; + + if (self.accessToken) { + tokenSecret = [self accessTokenSecret]; + } else if (self.requestToken) { + tokenSecret = [self requestTokenSecret]; + } + + return tokenSecret; +} + +- (NSString *)signingKey { + NSString *consumerSecret = [[self consumerSecret] stringByAddingURIPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + NSString *tokenSecret = [[self tokenSecret] stringByAddingURIPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + + return [NSString stringWithFormat:@"%@%&%@", consumerSecret, tokenSecret]; +} + +#pragma mark - + +- (NSString *)timestamp { + return [NSString stringWithFormat:@"%d", (int)[[NSDate date] timeIntervalSince1970]]; +} + +- (NSString *)signatureMethod { + return [self.store objectForKey:kMPOAuthSignatureMethod]; +} + +- (NSArray *)oauthParameters { + NSMutableArray *oauthParameters = [[NSMutableArray alloc] initWithCapacity:5]; + MPURLRequestParameter *tokenParameter = [self oauthTokenParameter]; + + [oauthParameters addObject:[self oauthConsumerKeyParameter]]; + if (tokenParameter) [oauthParameters addObject:tokenParameter]; + [oauthParameters addObject:[self oauthSignatureMethodParameter]]; + [oauthParameters addObject:[self oauthTimestampParameter]]; + [oauthParameters addObject:[self oauthNonceParameter]]; + [oauthParameters addObject:[self oauthVersionParameter]]; + + return [oauthParameters autorelease]; +} + +- (void)setSignatureMethod:(NSString *)inSignatureMethod { + [self.store setObject:inSignatureMethod forKey:kMPOAuthSignatureMethod]; +} + +- (MPURLRequestParameter *)oauthConsumerKeyParameter { + MPURLRequestParameter *aRequestParameter = [[MPURLRequestParameter alloc] init]; + aRequestParameter.name = @"oauth_consumer_key"; + aRequestParameter.value = self.consumerKey; + + return [aRequestParameter autorelease]; +} + +- (MPURLRequestParameter *)oauthTokenParameter { + MPURLRequestParameter *aRequestParameter = nil; + + if (self.accessToken || self.requestToken) { + aRequestParameter = [[MPURLRequestParameter alloc] init]; + aRequestParameter.name = @"oauth_token"; + + if (self.accessToken) { + aRequestParameter.value = self.accessToken; + } else if (self.requestToken) { + aRequestParameter.value = self.requestToken; + } + } + + return [aRequestParameter autorelease]; +} + +- (MPURLRequestParameter *)oauthSignatureMethodParameter { + MPURLRequestParameter *aRequestParameter = [[MPURLRequestParameter alloc] init]; + aRequestParameter.name = @"oauth_signature_method"; + aRequestParameter.value = self.signatureMethod; + + return [aRequestParameter autorelease]; +} + +- (MPURLRequestParameter *)oauthTimestampParameter { + MPURLRequestParameter *aRequestParameter = [[MPURLRequestParameter alloc] init]; + aRequestParameter.name = @"oauth_timestamp"; + aRequestParameter.value = self.timestamp; + + return [aRequestParameter autorelease]; +} + +- (MPURLRequestParameter *)oauthNonceParameter { + MPURLRequestParameter *aRequestParameter = [[MPURLRequestParameter alloc] init]; + aRequestParameter.name = @"oauth_nonce"; + + NSString *generatedNonce = nil; + CFUUIDRef generatedUUID = CFUUIDCreate(kCFAllocatorDefault); + + generatedNonce = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, generatedUUID); + CFRelease(generatedUUID); + + aRequestParameter.value = generatedNonce; + [generatedNonce release]; + + return [aRequestParameter autorelease]; +} + +- (MPURLRequestParameter *)oauthVersionParameter { + MPURLRequestParameter *versionParameter = [self.store objectForKey:@"versionParameter"]; + + if (!versionParameter) { + versionParameter = [[MPURLRequestParameter alloc] init]; + versionParameter.name = @"oauth_version"; + versionParameter.value = @"1.0"; + [versionParameter autorelease]; + } + + return versionParameter; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialStore.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialStore.h new file mode 100644 index 0000000..27dbfaa --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthCredentialStore.h @@ -0,0 +1,33 @@ +// +// MPOAuthCredentialStore.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.06. +// Copyright 2008 matrixPointer. All rights reserved. +// + +extern NSString *kMPOAuthCredentialConsumerKey; +extern NSString *kMPOAuthCredentialConsumerSecret; +extern NSString *kMPOAuthCredentialUsername; +extern NSString *kMPOAuthCredentialPassword; +extern NSString *kMPOAuthCredentialRequestToken; +extern NSString *kMPOAuthCredentialRequestTokenSecret; +extern NSString *kMPOAuthCredentialAccessToken; +extern NSString *kMPOAuthCredentialAccessTokenSecret; +extern NSString *kMPOAuthCredentialSessionHandle; +extern NSString *kMPOAuthCredentialRealm; + +@protocol MPOAuthCredentialStore + +@property (nonatomic, readonly) NSString *consumerKey; +@property (nonatomic, readonly) NSString *consumerSecret; +@property (nonatomic, readonly) NSString *username; +@property (nonatomic, readonly) NSString *password; +@property (nonatomic, readonly, retain) NSString *requestToken; +@property (nonatomic, readonly, retain) NSString *requestTokenSecret; +@property (nonatomic, readonly, retain) NSString *accessToken; +@property (nonatomic, readonly, retain) NSString *accessTokenSecret; + +- (NSString *)credentialNamed:(NSString *)inCredentialName; +- (void)discardOAuthCredentials; +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthParameterFactory.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthParameterFactory.h new file mode 100644 index 0000000..7d75ca3 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthParameterFactory.h @@ -0,0 +1,28 @@ +// +// MPOAuthParameterFactory.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.06. +// Copyright 2008 matrixPointer. All rights reserved. +// + +extern NSString *kMPOAuthSignatureMethod; + +@class MPURLRequestParameter; + +@protocol MPOAuthParameterFactory + +@property (nonatomic, readwrite, retain) NSString *signatureMethod; +@property (nonatomic, readonly) NSString *signingKey; +@property (nonatomic, readonly) NSString *timestamp; + +- (NSArray *)oauthParameters; + +- (MPURLRequestParameter *)oauthConsumerKeyParameter; +- (MPURLRequestParameter *)oauthTokenParameter; +- (MPURLRequestParameter *)oauthSignatureMethodParameter; +- (MPURLRequestParameter *)oauthTimestampParameter; +- (MPURLRequestParameter *)oauthNonceParameter; +- (MPURLRequestParameter *)oauthVersionParameter; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthSignatureParameter.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthSignatureParameter.h new file mode 100644 index 0000000..08ec7c5 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthSignatureParameter.h @@ -0,0 +1,28 @@ +// +// MPOAuthSignatureParameter.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.07. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import +#import "MPURLRequestParameter.h" + +#define kMPOAuthSignatureMethodPlaintext @"PLAINTEXT" +#define kMPOAuthSignatureMethodHMACSHA1 @"HMAC-SHA1" +#define kMPOAuthSignatureMethodRSASHA1 @"RSA-SHA1" + +@class MPOAuthURLRequest; + +@interface MPOAuthSignatureParameter : MPURLRequestParameter { + +} + ++ (NSString *)signatureBaseStringUsingParameterString:(NSString *)inParameterString forRequest:(MPOAuthURLRequest *)inRequest; ++ (NSString *)HMAC_SHA1SignatureForText:(NSString *)inText usingSecret:(NSString *)inSecret; + +- (id)initWithText:(NSString *)inText andSecret:(NSString *)inSecret forRequest:(MPOAuthURLRequest *)inRequest usingMethod:(NSString *)inMethod; + + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthSignatureParameter.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthSignatureParameter.m new file mode 100644 index 0000000..b0b3538 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthSignatureParameter.m @@ -0,0 +1,81 @@ +// +// MPOAuthSignatureParameter.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.07. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "MPOAuthSignatureParameter.h" +#import "MPOAuthURLRequest.h" +#import "NSString+URLEscapingAdditions.h" +#import "NSURL+MPURLParameterAdditions.h" + +#import +#include "Base64Transcoder.h" + +@interface MPOAuthSignatureParameter () +- (id)initUsingHMAC_SHA1WithText:(NSString *)inText andSecret:(NSString *)inSecret forRequest:(MPOAuthURLRequest *)inRequest; +@end + +@implementation MPOAuthSignatureParameter + ++ (NSString *)signatureBaseStringUsingParameterString:(NSString *)inParameterString forRequest:(MPOAuthURLRequest *)inRequest { + return [NSString stringWithFormat:@"%@&%@&%@", [inRequest HTTPMethod], + [[inRequest.url absoluteNormalizedString] stringByAddingURIPercentEscapesUsingEncoding:NSUTF8StringEncoding], + [inParameterString stringByAddingURIPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; +} + ++ (NSString *)HMAC_SHA1SignatureForText:(NSString *)inText usingSecret:(NSString *)inSecret { + NSData *secretData = [inSecret dataUsingEncoding:NSUTF8StringEncoding]; + NSData *textData = [inText dataUsingEncoding:NSUTF8StringEncoding]; + unsigned char result[CC_SHA1_DIGEST_LENGTH]; + + CCHmacContext hmacContext; + bzero(&hmacContext, sizeof(CCHmacContext)); + CCHmacInit(&hmacContext, kCCHmacAlgSHA1, secretData.bytes, secretData.length); + CCHmacUpdate(&hmacContext, textData.bytes, textData.length); + CCHmacFinal(&hmacContext, result); + + //Base64 Encoding + char base64Result[32]; + size_t theResultLength = 32; + Base64EncodeData(result, 20, base64Result, &theResultLength); + NSData *theData = [NSData dataWithBytes:base64Result length:theResultLength]; + NSString *base64EncodedResult = [[[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding] autorelease]; + + return base64EncodedResult; +} + +- (id)initWithText:(NSString *)inText andSecret:(NSString *)inSecret forRequest:(MPOAuthURLRequest *)inRequest usingMethod:(NSString *)inMethod { + if ([inMethod isEqual:kMPOAuthSignatureMethodHMACSHA1]) { + self = [self initUsingHMAC_SHA1WithText:inText andSecret:inSecret forRequest:inRequest]; + } else if ([inMethod isEqualToString:kMPOAuthSignatureMethodPlaintext]) { + if (self = [super init]) { + self.name = @"oauth_signature"; + self.value = inSecret; + } + } else { + [self release]; + self = nil; + [NSException raise:@"Unsupported Signature Method" format:@"The signature method \"%@\" is not currently support by MPOAuthConnection", inMethod]; + } + + return self; +} + +- (id)initUsingHMAC_SHA1WithText:(NSString *)inText andSecret:(NSString *)inSecret forRequest:(MPOAuthURLRequest *)inRequest { + if (self = [super init]) { + NSString *signatureBaseString = [MPOAuthSignatureParameter signatureBaseStringUsingParameterString:inText forRequest:inRequest]; + + self.name = @"oauth_signature"; + self.value = [MPOAuthSignatureParameter HMAC_SHA1SignatureForText:signatureBaseString usingSecret:inSecret]; + } + return self; +} + +- (oneway void)dealloc { + [super dealloc]; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLRequest.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLRequest.h new file mode 100644 index 0000000..0ad4b3e --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLRequest.h @@ -0,0 +1,32 @@ +// +// MPOAuthURLRequest.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import + + +@interface MPOAuthURLRequest : NSObject { +@private + NSURL *_url; + NSString *_httpMethod; + NSURLRequest *_urlRequest; + NSMutableArray *_parameters; +} + +@property (nonatomic, readwrite, retain) NSURL *url; +@property (nonatomic, readwrite, retain) NSString *HTTPMethod; +@property (nonatomic, readonly, retain) NSURLRequest *urlRequest; +@property (nonatomic, readwrite, retain) NSMutableArray *parameters; + +- (id)initWithURL:(NSURL *)inURL andParameters:(NSArray *)parameters; +- (id)initWithURLRequest:(NSURLRequest *)inRequest; + +- (void)addParameters:(NSArray *)inParameters; + +- (NSMutableURLRequest*)urlRequestSignedWithSecret:(NSString *)inSecret usingMethod:(NSString *)inScheme; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLRequest.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLRequest.m new file mode 100644 index 0000000..a4430e4 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLRequest.m @@ -0,0 +1,100 @@ +// +// MPOAuthURLRequest.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "MPOAuthURLRequest.h" +#import "MPURLRequestParameter.h" +#import "MPOAuthSignatureParameter.h" +#import "MPDebug.h" + +#import "NSURL+MPURLParameterAdditions.h" +#import "NSString+URLEscapingAdditions.h" + +@interface MPOAuthURLRequest () +@property (nonatomic, readwrite, retain) NSURLRequest *urlRequest; +@end + +@implementation MPOAuthURLRequest + +- (id)initWithURL:(NSURL *)inURL andParameters:(NSArray *)inParameters { + if (self = [super init]) { + self.url = inURL; + _parameters = inParameters ? [inParameters mutableCopy] : [[NSMutableArray alloc] initWithCapacity:10]; + self.HTTPMethod = @"GET"; + } + return self; +} + +- (id)initWithURLRequest:(NSURLRequest *)inRequest { + if (self = [super init]) { + self.url = [[inRequest URL] urlByRemovingQuery]; + self.parameters = [[MPURLRequestParameter parametersFromString:[[inRequest URL] query]] mutableCopy]; + self.HTTPMethod = [inRequest HTTPMethod]; + } + return self; +} + +- (oneway void)dealloc { + self.url = nil; + self.HTTPMethod = nil; + self.urlRequest = nil; + self.parameters = nil; + + [super dealloc]; +} + +@synthesize url = _url; +@synthesize HTTPMethod = _httpMethod; +@synthesize urlRequest = _urlRequest; +@synthesize parameters = _parameters; + +#pragma mark - + +- (NSMutableURLRequest*)urlRequestSignedWithSecret:(NSString *)inSecret usingMethod:(NSString *)inScheme { + [self.parameters sortUsingSelector:@selector(compare:)]; + + NSMutableURLRequest *aRequest = [[NSMutableURLRequest alloc] init]; + NSMutableString *parameterString = [[NSMutableString alloc] initWithString:[MPURLRequestParameter parameterStringForParameters:self.parameters]]; + MPOAuthSignatureParameter *signatureParameter = [[MPOAuthSignatureParameter alloc] initWithText:parameterString andSecret:inSecret forRequest:self usingMethod:inScheme]; + [parameterString appendFormat:@"&%@", [signatureParameter URLEncodedParameterString]]; + + [aRequest setHTTPMethod:self.HTTPMethod]; + + if ([[self HTTPMethod] isEqualToString:@"GET"] && [self.parameters count]) { + NSString *urlString = [NSString stringWithFormat:@"%@?%@", [self.url absoluteString], parameterString]; + MPLog( @"urlString - %@", urlString); + + [aRequest setURL:[NSURL URLWithString:urlString]]; + } else if ([[self HTTPMethod] isEqualToString:@"POST"]) { + NSData *postData = [parameterString dataUsingEncoding:NSUTF8StringEncoding]; + MPLog(@"urlString - %@", self.url); + MPLog(@"postDataString - %@", parameterString); + + [aRequest setURL:self.url]; + [aRequest setValue:[NSString stringWithFormat:@"%d", [postData length]] forHTTPHeaderField:@"Content-Length"]; + [aRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; + [aRequest setHTTPBody:postData]; + } else { + [NSException raise:@"UnhandledHTTPMethodException" format:@"The requested HTTP method, %@, is not supported", self.HTTPMethod]; + } + + [parameterString release]; + [signatureParameter release]; + + self.urlRequest = aRequest; + [aRequest release]; + + return aRequest; +} + +#pragma mark - + +- (void)addParameters:(NSArray *)inParameters { + [self.parameters addObjectsFromArray:inParameters]; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLResponse.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLResponse.h new file mode 100644 index 0000000..c3c5cc6 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLResponse.h @@ -0,0 +1,20 @@ +// +// MPOAuthURLResponse.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import + + +@interface MPOAuthURLResponse : NSObject { + NSURLResponse *_urlResponse; + NSDictionary *_oauthParameters; +} + +@property (nonatomic, readonly, retain) NSURLResponse *urlResponse; +@property (nonatomic, readonly, retain) NSDictionary *oauthParameters; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLResponse.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLResponse.m new file mode 100644 index 0000000..73c87a3 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPOAuthURLResponse.m @@ -0,0 +1,35 @@ +// +// MPOAuthURLResponse.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "MPOAuthURLResponse.h" + +@interface MPOAuthURLResponse () +@property (nonatomic, readwrite, retain) NSURLResponse *urlResponse; +@property (nonatomic, readwrite, retain) NSDictionary *oauthParameters; +@end + +@implementation MPOAuthURLResponse + +- (id)init { + if (self = [super init]) { + + } + return self; +} + +- (oneway void)dealloc { + self.urlResponse = nil; + self.oauthParameters = nil; + + [super dealloc]; +} + +@synthesize urlResponse = _urlResponse; +@synthesize oauthParameters = _oauthParameters; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPURLRequestParameter.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPURLRequestParameter.h new file mode 100644 index 0000000..36e53bc --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPURLRequestParameter.h @@ -0,0 +1,30 @@ +// +// MPURLParameter.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import + + +@interface MPURLRequestParameter : NSObject { + NSString *_name; + NSString *_value; +} + +@property (nonatomic, readwrite, copy) NSString *name; +@property (nonatomic, readwrite, copy) NSString *value; + ++ (NSArray *)parametersFromString:(NSString *)inString; ++ (NSArray *)parametersFromDictionary:(NSDictionary *)inDictionary; ++ (NSDictionary *)parameterDictionaryFromString:(NSString *)inString; ++ (NSString *)parameterStringForParameters:(NSArray *)inParameters; ++ (NSString *)parameterStringForDictionary:(NSDictionary *)inParameterDictionary; + +- (id)initWithName:(NSString *)inName andValue:(NSString *)inValue; + +- (NSString *)URLEncodedParameterString; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/MPURLRequestParameter.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPURLRequestParameter.m new file mode 100644 index 0000000..64b811f --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/MPURLRequestParameter.m @@ -0,0 +1,158 @@ +// +// MPURLParameter.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "MPURLRequestParameter.h" +#import "NSString+URLEscapingAdditions.h" + +@implementation MPURLRequestParameter + ++ (NSArray *)parametersFromString:(NSString *)inString { + NSMutableArray *foundParameters = [NSMutableArray arrayWithCapacity:10]; + NSScanner *parameterScanner = [[NSScanner alloc] initWithString:inString]; + NSString *name = nil; + NSString *value = nil; + MPURLRequestParameter *currentParameter = nil; + + while (![parameterScanner isAtEnd]) { + name = nil; + value = nil; + + [parameterScanner scanUpToString:@"=" intoString:&name]; + [parameterScanner scanString:@"=" intoString:NULL]; + [parameterScanner scanUpToString:@"&" intoString:&value]; + [parameterScanner scanString:@"&" intoString:NULL]; + + currentParameter = [[MPURLRequestParameter alloc] init]; + currentParameter.name = name; + currentParameter.value = [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + + [foundParameters addObject:currentParameter]; + + [currentParameter release]; + } + + [parameterScanner release]; + + return foundParameters; +} + ++ (NSArray *)parametersFromDictionary:(NSDictionary *)inDictionary { + NSMutableArray *parameterArray = [[NSMutableArray alloc] init]; + MPURLRequestParameter *aURLParameter = nil; + + for (NSString *aKey in [inDictionary allKeys]) { + aURLParameter = [[MPURLRequestParameter alloc] init]; + aURLParameter.name = aKey; + aURLParameter.value = [inDictionary objectForKey:aKey]; + + [parameterArray addObject:aURLParameter]; + [aURLParameter release]; + } + + return [parameterArray autorelease]; +} + ++ (NSDictionary *)parameterDictionaryFromString:(NSString *)inString { + NSMutableDictionary *foundParameters = [NSMutableDictionary dictionaryWithCapacity:10]; + if (inString) { + NSScanner *parameterScanner = [[NSScanner alloc] initWithString:inString]; + NSString *name = nil; + NSString *value = nil; + + while (![parameterScanner isAtEnd]) { + name = nil; + value = nil; + + [parameterScanner scanUpToString:@"=" intoString:&name]; + [parameterScanner scanString:@"=" intoString:NULL]; + [parameterScanner scanUpToString:@"&" intoString:&value]; + [parameterScanner scanString:@"&" intoString:NULL]; + + [foundParameters setObject:[value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:name]; + } + + [parameterScanner release]; + } + return foundParameters; +} + ++ (NSString *)parameterStringForParameters:(NSArray *)inParameters { + NSMutableString *queryString = [[NSMutableString alloc] init]; + int i = 0; + int parameterCount = [inParameters count]; + MPURLRequestParameter *aParameter = nil; + + for (; i < parameterCount; i++) { + aParameter = [inParameters objectAtIndex:i]; + [queryString appendString:[aParameter URLEncodedParameterString]]; + + if (i < parameterCount - 1) { + [queryString appendString:@"&"]; + } + } + + return [queryString autorelease]; +} + ++ (NSString *)parameterStringForDictionary:(NSDictionary *)inParameterDictionary { + NSArray *parameters = [self parametersFromDictionary:inParameterDictionary]; + NSString *queryString = [self parameterStringForParameters:parameters]; + + return queryString; +} + +#pragma mark - + +- (id)init { + if (self = [super init]) { + + } + return self; +} + +- (id)initWithName:(NSString *)inName andValue:(NSString *)inValue { + if (self = [super init]) { + self.name = inName; + self.value = inValue; + } + return self; +} + +- (oneway void)dealloc { + self.name = nil; + self.value = nil; + + [super dealloc]; +} + +@synthesize name = _name; +@synthesize value = _value; + +#pragma mark - + +- (NSString *)URLEncodedParameterString { + return [NSString stringWithFormat:@"%@=%@", [self.name stringByAddingURIPercentEscapesUsingEncoding:NSUTF8StringEncoding], self.value ? [self.value stringByAddingURIPercentEscapesUsingEncoding:NSUTF8StringEncoding] : @""]; +} + +#pragma mark - + +- (NSComparisonResult)compare:(id)inObject { + NSComparisonResult result = [self.name compare:[(MPURLRequestParameter *)inObject name]]; + + if (result == NSOrderedSame) { + result = [self.value compare:[(MPURLRequestParameter *)inObject value]]; + } + + return result; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p %@>", NSStringFromClass([self class]), self, [self URLEncodedParameterString]]; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/NSString+URLEscapingAdditions.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSString+URLEscapingAdditions.h new file mode 100644 index 0000000..9be93a4 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSString+URLEscapingAdditions.h @@ -0,0 +1,21 @@ +// +// NSString+URLEscapingAdditions.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.07. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import + + +@interface NSString (MPURLEscapingAdditions) + +- (BOOL)isIPAddress; +- (NSString *)stringByAddingURIPercentEscapesUsingEncoding:(NSStringEncoding)inEncoding; + +@end + +@interface NSURL (MPURLEscapingAdditions) +- (NSString *)stringByAddingURIPercentEscapesUsingEncoding:(NSStringEncoding)inEncoding; +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/NSString+URLEscapingAdditions.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSString+URLEscapingAdditions.m new file mode 100644 index 0000000..a5d4587 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSString+URLEscapingAdditions.m @@ -0,0 +1,60 @@ +// +// NSString+URLEscapingAdditions.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.07. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "NSString+URLEscapingAdditions.h" + + +@implementation NSString (MPURLEscapingAdditions) + +- (BOOL)isIPAddress { + BOOL isIPAddress = NO; + NSArray *components = [self componentsSeparatedByString:@"."]; + NSCharacterSet *invalidCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"1234567890"] invertedSet]; + + if ([components count] == 4) { + NSString *part1 = [components objectAtIndex:0]; + NSString *part2 = [components objectAtIndex:1]; + NSString *part3 = [components objectAtIndex:2]; + NSString *part4 = [components objectAtIndex:3]; + + if ([part1 rangeOfCharacterFromSet:invalidCharacters].location == NSNotFound && + [part2 rangeOfCharacterFromSet:invalidCharacters].location == NSNotFound && + [part3 rangeOfCharacterFromSet:invalidCharacters].location == NSNotFound && + [part4 rangeOfCharacterFromSet:invalidCharacters].location == NSNotFound ) { + + if ([part1 intValue] < 255 && + [part2 intValue] < 255 && + [part3 intValue] < 255 && + [part4 intValue] < 255) { + isIPAddress = YES; + } + } + } + + return isIPAddress; +} + +- (NSString *)stringByAddingURIPercentEscapesUsingEncoding:(NSStringEncoding)inEncoding { + NSString *escapedString = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)self, + NULL, + (CFStringRef)@":/?=,!$&'()*+;[]@#", + CFStringConvertNSStringEncodingToEncoding(inEncoding)); + + return [escapedString autorelease]; +} + +@end + +@implementation NSURL (MPURLEscapingAdditions) + +- (NSString *)stringByAddingURIPercentEscapesUsingEncoding:(NSStringEncoding)inEncoding { + return [[self absoluteString] stringByAddingURIPercentEscapesUsingEncoding:inEncoding]; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURL+MPURLParameterAdditions.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURL+MPURLParameterAdditions.h new file mode 100644 index 0000000..cea636d --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURL+MPURLParameterAdditions.h @@ -0,0 +1,21 @@ +// +// NSURL+MPURLParameterAdditions.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.08. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import + + +@interface NSURL (MPURLParameterAdditions) + +- (NSURL *)urlByAddingParameters:(NSArray *)inParameters; +- (NSURL *)urlByAddingParameterDictionary:(NSDictionary *)inParameters; +- (NSURL *)urlByRemovingQuery; +- (NSString *)absoluteNormalizedString; + +- (BOOL)domainMatches:(NSString *)inString; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURL+MPURLParameterAdditions.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURL+MPURLParameterAdditions.m new file mode 100644 index 0000000..aca3286 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURL+MPURLParameterAdditions.m @@ -0,0 +1,98 @@ +// +// NSURL+MPURLParameterAdditions.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.08. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "NSURL+MPURLParameterAdditions.h" +#import "MPURLRequestParameter.h" +#import "NSString+URLEscapingAdditions.h" + +@implementation NSURL (MPURLParameterAdditions) + +- (NSURL *)urlByAddingParameters:(NSArray *)inParameters { + NSMutableArray *parameters = [[NSMutableArray alloc] init]; + NSString *queryString = [self query]; + NSString *absoluteString = [self absoluteString]; + NSRange parameterRange = [absoluteString rangeOfString:@"?"]; + + if (parameterRange.location != NSNotFound) { + parameterRange.length = [absoluteString length] - parameterRange.location; + [parameters addObjectsFromArray:[MPURLRequestParameter parametersFromString:queryString]]; + absoluteString = [absoluteString substringToIndex:parameterRange.location]; + } + + [parameters addObjectsFromArray:inParameters]; + + return [NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", absoluteString, [MPURLRequestParameter parameterStringForParameters:[parameters autorelease]]]]; +} + +- (NSURL *)urlByAddingParameterDictionary:(NSDictionary *)inParameterDictionary { + NSMutableDictionary *parameterDictionary = [inParameterDictionary mutableCopy]; + NSString *queryString = [self query]; + NSString *absoluteString = [self absoluteString]; + NSRange parameterRange = [absoluteString rangeOfString:@"?"]; + NSURL *composedURL = self; + + if (parameterRange.location != NSNotFound) { + parameterRange.length = [absoluteString length] - parameterRange.location; + + //[parameterDictionary addEntriesFromDictionary:inParameterDictionary]; + [parameterDictionary addEntriesFromDictionary:[MPURLRequestParameter parameterDictionaryFromString:queryString]]; + + composedURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", [absoluteString substringToIndex:parameterRange.location], [MPURLRequestParameter parameterStringForDictionary:parameterDictionary]]]; + } else if ([parameterDictionary count]) { + composedURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", absoluteString, [MPURLRequestParameter parameterStringForDictionary:parameterDictionary]]]; + } + + [parameterDictionary release]; + + return composedURL; +} + +- (NSURL *)urlByRemovingQuery { + NSURL *composedURL = self; + NSString *absoluteString = [self absoluteString]; + NSRange queryRange = [absoluteString rangeOfString:@"?"]; + + if (queryRange.location != NSNotFound) { + NSString *urlSansQuery = [absoluteString substringToIndex:queryRange.location]; + composedURL = [NSURL URLWithString:urlSansQuery]; + } + + return composedURL; +} + +- (NSString *)absoluteNormalizedString { + NSString *normalizedString = [self absoluteString]; + + if ([[self path] length] == 0 && [[self query] length] == 0) { + normalizedString = [NSString stringWithFormat:@"%@/", [self absoluteString]]; + } + + return normalizedString; +} + +- (BOOL)domainMatches:(NSString *)inString { + BOOL matches = NO; + + NSString *domain = [self host]; + matches = [domain isIPAddress] && [domain isEqualToString:inString]; + + int domainLength = [domain length]; + int requestedDomainLength = [inString length]; + + if (!matches) { + if (domainLength > requestedDomainLength) { + matches = [domain rangeOfString:inString].location == (domainLength - requestedDomainLength); + } else if (domainLength == (requestedDomainLength - 1)) { + matches = ([inString compare:domain options:NSCaseInsensitiveSearch range:NSMakeRange(1, domainLength)] == NSOrderedSame); + } + } + + return matches; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURLResponse+Encoding.h b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURLResponse+Encoding.h new file mode 100644 index 0000000..40699ef --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURLResponse+Encoding.h @@ -0,0 +1,14 @@ +// +// NSURL+MPEncodingAdditions.h +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import + + +@interface NSURLResponse (EncodingAdditions) +- (NSStringEncoding)encoding; +@end diff --git a/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURLResponse+Encoding.m b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURLResponse+Encoding.m new file mode 100644 index 0000000..beab278 --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/MPOAuth/NSURLResponse+Encoding.m @@ -0,0 +1,27 @@ +// +// NSURL+MPEncodingAdditions.m +// MPOAuthConnection +// +// Created by Karl Adam on 08.12.05. +// Copyright 2008 matrixPointer. All rights reserved. +// + +#import "NSURLResponse+Encoding.h" + + +@implementation NSURLResponse (EncodingAdditions) + +- (NSStringEncoding)encoding { + NSStringEncoding encoding = NSUTF8StringEncoding; + + if ([self textEncodingName]) { + CFStringEncoding cfStringEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)[self textEncodingName]); + if (cfStringEncoding != kCFStringEncodingInvalidId) { + encoding = CFStringConvertEncodingToNSStringEncoding(cfStringEncoding); + } + } + + return encoding; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/NSString+Dropbox.h b/Classes/ThirdParty/DropboxSDK/NSString+Dropbox.h new file mode 100644 index 0000000..c261f4a --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/NSString+Dropbox.h @@ -0,0 +1,18 @@ +// +// NSString+Dropbox.h +// DropboxSDK +// +// Created by Brian Smith on 7/19/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + + +@interface NSString (Dropbox) + +// This will take a path for a resource and normalize so you can compare paths +- (NSString*)normalizedDropboxPath; + +// Normalizes both paths and compares them +- (BOOL)isEqualToDropboxPath:(NSString*)otherPath; + +@end diff --git a/Classes/ThirdParty/DropboxSDK/NSString+Dropbox.m b/Classes/ThirdParty/DropboxSDK/NSString+Dropbox.m new file mode 100644 index 0000000..ef665ce --- /dev/null +++ b/Classes/ThirdParty/DropboxSDK/NSString+Dropbox.m @@ -0,0 +1,23 @@ +// +// NSString+Dropbox.m +// DropboxSDK +// +// Created by Brian Smith on 7/19/10. +// Copyright 2010 Dropbox, Inc. All rights reserved. +// + +#import "NSString+Dropbox.h" + + +@implementation NSString (Dropbox) + +- (NSString*)normalizedDropboxPath { + if ([self isEqual:@"/"]) return @""; + return [[self lowercaseString] precomposedStringWithCanonicalMapping]; +} + +- (BOOL)isEqualToDropboxPath:(NSString*)otherPath { + return [[self normalizedDropboxPath] isEqualToString:[otherPath normalizedDropboxPath]]; +} + +@end diff --git a/Classes/ThirdParty/DropboxSDK/Resources/db_background.png b/Classes/ThirdParty/DropboxSDK/Resources/db_background.png new file mode 100644 index 0000000000000000000000000000000000000000..dbb153bbfc52d6ce4b54d094761ab239717d3543 GIT binary patch literal 4890 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 literal 0 HcmV?d00001 diff --git a/Classes/ThirdParty/DropboxSDK/Resources/db_create_account.png b/Classes/ThirdParty/DropboxSDK/Resources/db_create_account.png new file mode 100644 index 0000000000000000000000000000000000000000..5e1ee1566c55446e041b1dd447b42d7b9f369053 GIT binary patch literal 4600 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 KMNUMnLSTYm%2;av literal 0 HcmV?d00001 diff --git a/Classes/ThirdParty/DropboxSDK/Resources/db_create_account_button.png b/Classes/ThirdParty/DropboxSDK/Resources/db_create_account_button.png new file mode 100644 index 0000000000000000000000000000000000000000..f382dd9add212c9c802837160ac45a4c471047c3 GIT binary patch literal 6631 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 U7cc33nsEU+DdkU<62?LQ15g|r82|tP literal 0 HcmV?d00001 diff --git a/Classes/ThirdParty/DropboxSDK/Resources/db_create_account_button_down.png b/Classes/ThirdParty/DropboxSDK/Resources/db_create_account_button_down.png new file mode 100644 index 0000000000000000000000000000000000000000..9c56b98cf1b3433ba85c3f8b6ce145e89175259b GIT binary patch literal 6286 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 literal 0 HcmV?d00001 diff --git a/Classes/ThirdParty/DropboxSDK/Resources/db_link_button.png b/Classes/ThirdParty/DropboxSDK/Resources/db_link_button.png new file mode 100644 index 0000000000000000000000000000000000000000..3e14fe07298d753eaa32f034ecdb144b628820ed GIT binary patch literal 4912 zcmV-06VL34P)KLZ*U+Zy7GjkFk%D2)V=o*~W(^mO5d3B$RJTcCo3 z90*K|iOUu_2L$l(uZsXaT?YXG-KH@jQ5ycUlFJGch66+Z08Y{{#{=NR4s$L5&Rnrb z3=pdUaN?3<;sK%@fJv4h&=nvq159?3AkY<%=mnVUSV5pGAomDhvSVVhk^t2=0Jt$R zNlAd3>@deB0ctz|+&OVcVn8hb052mxB?C}f1i&^vAv+FGs{@b}pBFJsA zfCd+UL7XT{3}|=(&S#yThQvrYmNyvZ$ zRcJv61~7#sY%m#4aEA~4A%GB3h(j{ck%hU)$6_qUDwJRo%CHN+Vm}VyC~DDwbGU#G zbfE{g(1*u(jyL#-Zv;Uw2sR;4s1RC&9$`#a5VnLP;ZFDxfrOBVCK8EsB8SK)3W;K3 z1F@B;AodZ5i8`W*Xd}9aUZRf}Al?#RNs?rd98!bSBh5$~(uwpTgUCoSkra{h$U<@r zxrMACtH~2&BiT+~Bkz;X$q(cZMUtXO;Zlq#HWU}iG>VXtKoL>$DJv-(Ddm&{lzPf} z%2moe$_vUDDwQfn)uNhE?Wi790X2r2NzJDgQ-7iEp&q9;Q?F3(Q(sdDX)Kx=&46Y@ z^Q48)5@=%DQd%jkl6H*NLhGhIq`jw8=p4E(eInhR9zsu|=hBPlW%O$L8G0wZkN%E9 zVJI^68FmalBZ?tn6f!n5_AyQ~IvEcbADB$0I@6r#!VF=iG7Fd+n0uI~n4Qdr%+C^R ziSZJXBzz^JC2}N+CCVjENOVZ_NqmxIOKMBnN%~7BNajm!klZiXBzZ&fHH*g5WLdL( zS#hlStWs7rtA%x&^`6aU>#`l#A?ytH3U&qi6uX=KN{TMUm9m!-NM%T^l-e!TAaz6P ztu$L&U)n`_hIFoUsdSBWyYv$oQbtS0UM55)OJ=Q1wM?7LBUwULOLnqsnCu+cQrRQ2 zow6_GB;^d`Jmli#3gs%~n&j@w{gBs`pCTV2pC?}?UoU@C{x1bp1v>?yLY_jI!fA!u z3WFR?jw5F#XEA3trx$`h4^ z$_tctD_>B4t|Fsits+!epi-&Qrt(TvLDg0@N_DAfm1>vjCpAqqSG82N617^jd+Ky` zQ+0uQzIvs4hx&UBbq!aIG>wfKXEdH@N^9C^Mr#&p9@D(5Mc3kKg=;OFNpe3iXcZ^-qwW;4&d=Lgj>ReTu%deuDmH{fqiv42%pS z4AvN&HF#sFZ5V90!m!@(rO`N}K%-?wbw)3YwTuIemm8ller=*{5@J$p(rEI*)X+4_ zwA8fC^t+jrS&G?Cvuoxob7%9p=7-E5@zi;NydvH?-WLm=MUurXiyM|QmR^>NEbA=a zS{YfzS#7uKv6iv+vR-1{VEt(#Z({1i-zMIlq&6vdQpuza8@i2)O@U3l%|}}c+jQG1 z+b4E9cF}e_>~7gB+Y9VB*#9wEYO?R-qRAJh(5JXfDV);mKsY!#EOcme_~Gd2Sm1cp z@yFDuQx{Hca)Ohy(-NmvXPUF8^GfG-7q*L^%X*h?S0&dl*KMx%+{U{lxb1U$>CST( zyVto7dN_G3^Jw>!_6+je>Uqyg$1BCF#_OZ^Wbeh^7k#9Cf_=96^!pn5&i1Y2gYU&J z;rCACPD`G4WZG9h7r)hhJ^ot$N&ZLtzXiAltP8j~U3YrM^!h+*pnqUl;Nu{Rp!}dV z0Y?xmI4Jll*dusD@cj^zki3wKp`6gz(8HlW!uVljVFN;2;c{V5xNf*Oyd^>*A}-=+ zBqcH^vNH0+47VAZW;}_qi7JY^HPd8f!OX5`?PziI#TeC?w3x$=j2?4xuUt9xyHFgxliZ0&#TNM^P=+_=WEShF#k^el>BY^-xfqHI9;G#kY8|T zp~J$R3x^g(FKS+_vv}F!r%Sw-R2NDYiVC}zPFlKk>EN>HWi89~m#

=L*4!x|NzM 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

=L*4!x|NzM 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 literal 0 HcmV?d00001 diff --git a/Classes/ThirdParty/DropboxSDK/Resources/db_link_header.png b/Classes/ThirdParty/DropboxSDK/Resources/db_link_header.png new file mode 100644 index 0000000000000000000000000000000000000000..becf02055d0b141d6fb6409e93f664e61897a6db GIT binary patch literal 6540 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 l^LM)jV+{vtIIuAs_@Fw5?{rA4N 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 f{Dak*93~= literal 0 HcmV?d00001 diff --git a/MyKeePass.xcodeproj/project.pbxproj b/MyKeePass.xcodeproj/project.pbxproj index 8cfcb8b..61dc176 100755 --- a/MyKeePass.xcodeproj/project.pbxproj +++ b/MyKeePass.xcodeproj/project.pbxproj @@ -10,6 +10,51 @@ 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; 288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; }; + 485275FE137698B0007CDA4F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 485275FD137698B0007CDA4F /* Security.framework */; }; + 485276571376992B007CDA4F /* DBAccountInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276021376992B007CDA4F /* DBAccountInfo.m */; }; + 485276581376992B007CDA4F /* DBCreateAccountController.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276041376992B007CDA4F /* DBCreateAccountController.m */; }; + 485276591376992B007CDA4F /* DBError.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276061376992B007CDA4F /* DBError.m */; }; + 4852765A1376992B007CDA4F /* DBLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276081376992B007CDA4F /* DBLoadingView.m */; }; + 4852765B1376992B007CDA4F /* DBLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852760A1376992B007CDA4F /* DBLoginController.m */; }; + 4852765C1376992B007CDA4F /* DBMetadata.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852760C1376992B007CDA4F /* DBMetadata.m */; }; + 4852765D1376992B007CDA4F /* DBQuota.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852760E1376992B007CDA4F /* DBQuota.m */; }; + 4852765E1376992B007CDA4F /* DBRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276101376992B007CDA4F /* DBRequest.m */; }; + 4852765F1376992B007CDA4F /* DBRestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276121376992B007CDA4F /* DBRestClient.m */; }; + 485276601376992B007CDA4F /* DBSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276141376992B007CDA4F /* DBSession.m */; }; + 485276611376992B007CDA4F /* NSObject+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276191376992B007CDA4F /* NSObject+SBJSON.m */; }; + 485276621376992B007CDA4F /* NSString+SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852761B1376992B007CDA4F /* NSString+SBJSON.m */; }; + 485276631376992B007CDA4F /* SBJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852761D1376992B007CDA4F /* SBJSON.m */; }; + 485276641376992B007CDA4F /* SBJsonBase.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852761F1376992B007CDA4F /* SBJsonBase.m */; }; + 485276651376992B007CDA4F /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276211376992B007CDA4F /* SBJsonParser.m */; }; + 485276661376992B007CDA4F /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276231376992B007CDA4F /* SBJsonWriter.m */; }; + 485276671376992B007CDA4F /* Base64Transcoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 485276261376992B007CDA4F /* Base64Transcoder.c */; }; + 485276681376992B007CDA4F /* MPOAuthAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852762B1376992B007CDA4F /* MPOAuthAPI.m */; }; + 485276691376992B007CDA4F /* MPOAuthAPIRequestLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852762D1376992B007CDA4F /* MPOAuthAPIRequestLoader.m */; }; + 4852766A1376992B007CDA4F /* MPOAuthAuthenticationMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852762F1376992B007CDA4F /* MPOAuthAuthenticationMethod.m */; }; + 4852766B1376992B007CDA4F /* MPOAuthAuthenticationMethodOAuth.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276311376992B007CDA4F /* MPOAuthAuthenticationMethodOAuth.m */; }; + 4852766C1376992B007CDA4F /* MPOAuthConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276331376992B007CDA4F /* MPOAuthConnection.m */; }; + 4852766D1376992B007CDA4F /* MPOAuthCredentiaIConcreteStore+KeychainAdditionsMac.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276341376992B007CDA4F /* MPOAuthCredentiaIConcreteStore+KeychainAdditionsMac.m */; }; + 4852766E1376992B007CDA4F /* MPOAuthCredentialConcreteStore+KeychainAdditionsiPhone.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276361376992B007CDA4F /* MPOAuthCredentialConcreteStore+KeychainAdditionsiPhone.m */; }; + 4852766F1376992B007CDA4F /* MPOAuthCredentialConcreteStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276381376992B007CDA4F /* MPOAuthCredentialConcreteStore.m */; }; + 485276701376992B007CDA4F /* MPOAuthSignatureParameter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852763C1376992B007CDA4F /* MPOAuthSignatureParameter.m */; }; + 485276711376992B007CDA4F /* MPOAuthURLRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852763E1376992B007CDA4F /* MPOAuthURLRequest.m */; }; + 485276721376992B007CDA4F /* MPOAuthURLResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276401376992B007CDA4F /* MPOAuthURLResponse.m */; }; + 485276731376992B007CDA4F /* MPURLRequestParameter.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276421376992B007CDA4F /* MPURLRequestParameter.m */; }; + 485276741376992B007CDA4F /* NSString+URLEscapingAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276441376992B007CDA4F /* NSString+URLEscapingAdditions.m */; }; + 485276751376992B007CDA4F /* NSURL+MPURLParameterAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276461376992B007CDA4F /* NSURL+MPURLParameterAdditions.m */; }; + 485276761376992B007CDA4F /* NSURLResponse+Encoding.m in Sources */ = {isa = PBXBuildFile; fileRef = 485276481376992B007CDA4F /* NSURLResponse+Encoding.m */; }; + 485276771376992B007CDA4F /* NSString+Dropbox.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852764A1376992B007CDA4F /* NSString+Dropbox.m */; }; + 485276781376992B007CDA4F /* db_background.png in Resources */ = {isa = PBXBuildFile; fileRef = 4852764C1376992B007CDA4F /* db_background.png */; }; + 485276791376992B007CDA4F /* db_create_account.png in Resources */ = {isa = PBXBuildFile; fileRef = 4852764D1376992B007CDA4F /* db_create_account.png */; }; + 4852767A1376992B007CDA4F /* db_create_account@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 4852764E1376992B007CDA4F /* db_create_account@2x.png */; }; + 4852767B1376992B007CDA4F /* db_create_account_button.png in Resources */ = {isa = PBXBuildFile; fileRef = 4852764F1376992B007CDA4F /* db_create_account_button.png */; }; + 4852767C1376992B007CDA4F /* db_create_account_button_down.png in Resources */ = {isa = PBXBuildFile; fileRef = 485276501376992B007CDA4F /* db_create_account_button_down.png */; }; + 4852767D1376992B007CDA4F /* db_link_button.png in Resources */ = {isa = PBXBuildFile; fileRef = 485276511376992B007CDA4F /* db_link_button.png */; }; + 4852767E1376992B007CDA4F /* db_link_button_down.png in Resources */ = {isa = PBXBuildFile; fileRef = 485276521376992B007CDA4F /* db_link_button_down.png */; }; + 4852767F1376992B007CDA4F /* db_link_header.png in Resources */ = {isa = PBXBuildFile; fileRef = 485276531376992B007CDA4F /* db_link_header.png */; }; + 485276801376992B007CDA4F /* db_link_header@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 485276541376992B007CDA4F /* db_link_header@2x.png */; }; + 485276811376992B007CDA4F /* db_logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 485276551376992B007CDA4F /* db_logo.png */; }; + 485276821376992B007CDA4F /* db_logo@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 485276561376992B007CDA4F /* db_logo@2x.png */; }; F716539E115DBD090017DC12 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = F716539D115DBD090017DC12 /* Default.png */; }; F78F24AF10E4740C0073C213 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F78F24AE10E4740C0073C213 /* CFNetwork.framework */; }; F7DA414B10CB60FC00F1D072 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7DA414A10CB60FC00F1D072 /* QuartzCore.framework */; }; @@ -116,6 +161,90 @@ 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 288765FC0DF74451002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 32CA4F630368D1EE00C91783 /* MyKeePass_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyKeePass_Prefix.pch; sourceTree = ""; }; + 485275FD137698B0007CDA4F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 485275FF1376990C007CDA4F /* DropboxAPIKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropboxAPIKeys.h; sourceTree = ""; }; + 485276011376992B007CDA4F /* DBAccountInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBAccountInfo.h; sourceTree = ""; }; + 485276021376992B007CDA4F /* DBAccountInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBAccountInfo.m; sourceTree = ""; }; + 485276031376992B007CDA4F /* DBCreateAccountController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBCreateAccountController.h; sourceTree = ""; }; + 485276041376992B007CDA4F /* DBCreateAccountController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBCreateAccountController.m; sourceTree = ""; }; + 485276051376992B007CDA4F /* DBError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBError.h; sourceTree = ""; }; + 485276061376992B007CDA4F /* DBError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBError.m; sourceTree = ""; }; + 485276071376992B007CDA4F /* DBLoadingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBLoadingView.h; sourceTree = ""; }; + 485276081376992B007CDA4F /* DBLoadingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBLoadingView.m; sourceTree = ""; }; + 485276091376992B007CDA4F /* DBLoginController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBLoginController.h; sourceTree = ""; }; + 4852760A1376992B007CDA4F /* DBLoginController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBLoginController.m; sourceTree = ""; }; + 4852760B1376992B007CDA4F /* DBMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBMetadata.h; sourceTree = ""; }; + 4852760C1376992B007CDA4F /* DBMetadata.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBMetadata.m; sourceTree = ""; }; + 4852760D1376992B007CDA4F /* DBQuota.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBQuota.h; sourceTree = ""; }; + 4852760E1376992B007CDA4F /* DBQuota.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBQuota.m; sourceTree = ""; }; + 4852760F1376992B007CDA4F /* DBRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBRequest.h; sourceTree = ""; }; + 485276101376992B007CDA4F /* DBRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBRequest.m; sourceTree = ""; }; + 485276111376992B007CDA4F /* DBRestClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBRestClient.h; sourceTree = ""; }; + 485276121376992B007CDA4F /* DBRestClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBRestClient.m; sourceTree = ""; }; + 485276131376992B007CDA4F /* DBSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DBSession.h; sourceTree = ""; }; + 485276141376992B007CDA4F /* DBSession.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DBSession.m; sourceTree = ""; }; + 485276151376992B007CDA4F /* DropboxSDK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropboxSDK.h; sourceTree = ""; }; + 485276171376992B007CDA4F /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSON.h; sourceTree = ""; }; + 485276181376992B007CDA4F /* NSObject+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+SBJSON.h"; sourceTree = ""; }; + 485276191376992B007CDA4F /* NSObject+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+SBJSON.m"; sourceTree = ""; }; + 4852761A1376992B007CDA4F /* NSString+SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+SBJSON.h"; sourceTree = ""; }; + 4852761B1376992B007CDA4F /* NSString+SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+SBJSON.m"; sourceTree = ""; }; + 4852761C1376992B007CDA4F /* SBJSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJSON.h; sourceTree = ""; }; + 4852761D1376992B007CDA4F /* SBJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJSON.m; sourceTree = ""; }; + 4852761E1376992B007CDA4F /* SBJsonBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonBase.h; sourceTree = ""; }; + 4852761F1376992B007CDA4F /* SBJsonBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonBase.m; sourceTree = ""; }; + 485276201376992B007CDA4F /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonParser.h; sourceTree = ""; }; + 485276211376992B007CDA4F /* SBJsonParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonParser.m; sourceTree = ""; }; + 485276221376992B007CDA4F /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; + 485276231376992B007CDA4F /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; + 485276261376992B007CDA4F /* Base64Transcoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Base64Transcoder.c; sourceTree = ""; }; + 485276271376992B007CDA4F /* Base64Transcoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Base64Transcoder.h; sourceTree = ""; }; + 485276281376992B007CDA4F /* MPDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPDebug.h; sourceTree = ""; }; + 485276291376992B007CDA4F /* MPOAuth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuth.h; sourceTree = ""; }; + 4852762A1376992B007CDA4F /* MPOAuthAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthAPI.h; sourceTree = ""; }; + 4852762B1376992B007CDA4F /* MPOAuthAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOAuthAPI.m; sourceTree = ""; }; + 4852762C1376992B007CDA4F /* MPOAuthAPIRequestLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthAPIRequestLoader.h; sourceTree = ""; }; + 4852762D1376992B007CDA4F /* MPOAuthAPIRequestLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOAuthAPIRequestLoader.m; sourceTree = ""; }; + 4852762E1376992B007CDA4F /* MPOAuthAuthenticationMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthAuthenticationMethod.h; sourceTree = ""; }; + 4852762F1376992B007CDA4F /* MPOAuthAuthenticationMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOAuthAuthenticationMethod.m; sourceTree = ""; }; + 485276301376992B007CDA4F /* MPOAuthAuthenticationMethodOAuth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthAuthenticationMethodOAuth.h; sourceTree = ""; }; + 485276311376992B007CDA4F /* MPOAuthAuthenticationMethodOAuth.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOAuthAuthenticationMethodOAuth.m; sourceTree = ""; }; + 485276321376992B007CDA4F /* MPOAuthConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthConnection.h; sourceTree = ""; }; + 485276331376992B007CDA4F /* MPOAuthConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOAuthConnection.m; sourceTree = ""; }; + 485276341376992B007CDA4F /* MPOAuthCredentiaIConcreteStore+KeychainAdditionsMac.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPOAuthCredentiaIConcreteStore+KeychainAdditionsMac.m"; sourceTree = ""; }; + 485276351376992B007CDA4F /* MPOAuthCredentialConcreteStore+KeychainAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPOAuthCredentialConcreteStore+KeychainAdditions.h"; sourceTree = ""; }; + 485276361376992B007CDA4F /* MPOAuthCredentialConcreteStore+KeychainAdditionsiPhone.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPOAuthCredentialConcreteStore+KeychainAdditionsiPhone.m"; sourceTree = ""; }; + 485276371376992B007CDA4F /* MPOAuthCredentialConcreteStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthCredentialConcreteStore.h; sourceTree = ""; }; + 485276381376992B007CDA4F /* MPOAuthCredentialConcreteStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOAuthCredentialConcreteStore.m; sourceTree = ""; }; + 485276391376992B007CDA4F /* MPOAuthCredentialStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthCredentialStore.h; sourceTree = ""; }; + 4852763A1376992B007CDA4F /* MPOAuthParameterFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthParameterFactory.h; sourceTree = ""; }; + 4852763B1376992B007CDA4F /* MPOAuthSignatureParameter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthSignatureParameter.h; sourceTree = ""; }; + 4852763C1376992B007CDA4F /* MPOAuthSignatureParameter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOAuthSignatureParameter.m; sourceTree = ""; }; + 4852763D1376992B007CDA4F /* MPOAuthURLRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthURLRequest.h; sourceTree = ""; }; + 4852763E1376992B007CDA4F /* MPOAuthURLRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOAuthURLRequest.m; sourceTree = ""; }; + 4852763F1376992B007CDA4F /* MPOAuthURLResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPOAuthURLResponse.h; sourceTree = ""; }; + 485276401376992B007CDA4F /* MPOAuthURLResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPOAuthURLResponse.m; sourceTree = ""; }; + 485276411376992B007CDA4F /* MPURLRequestParameter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPURLRequestParameter.h; sourceTree = ""; }; + 485276421376992B007CDA4F /* MPURLRequestParameter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPURLRequestParameter.m; sourceTree = ""; }; + 485276431376992B007CDA4F /* NSString+URLEscapingAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+URLEscapingAdditions.h"; sourceTree = ""; }; + 485276441376992B007CDA4F /* NSString+URLEscapingAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+URLEscapingAdditions.m"; sourceTree = ""; }; + 485276451376992B007CDA4F /* NSURL+MPURLParameterAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+MPURLParameterAdditions.h"; sourceTree = ""; }; + 485276461376992B007CDA4F /* NSURL+MPURLParameterAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+MPURLParameterAdditions.m"; sourceTree = ""; }; + 485276471376992B007CDA4F /* NSURLResponse+Encoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLResponse+Encoding.h"; sourceTree = ""; }; + 485276481376992B007CDA4F /* NSURLResponse+Encoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLResponse+Encoding.m"; sourceTree = ""; }; + 485276491376992B007CDA4F /* NSString+Dropbox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Dropbox.h"; sourceTree = ""; }; + 4852764A1376992B007CDA4F /* NSString+Dropbox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Dropbox.m"; sourceTree = ""; }; + 4852764C1376992B007CDA4F /* db_background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = db_background.png; sourceTree = ""; }; + 4852764D1376992B007CDA4F /* db_create_account.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = db_create_account.png; sourceTree = ""; }; + 4852764E1376992B007CDA4F /* db_create_account@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "db_create_account@2x.png"; sourceTree = ""; }; + 4852764F1376992B007CDA4F /* db_create_account_button.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = db_create_account_button.png; sourceTree = ""; }; + 485276501376992B007CDA4F /* db_create_account_button_down.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = db_create_account_button_down.png; sourceTree = ""; }; + 485276511376992B007CDA4F /* db_link_button.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = db_link_button.png; sourceTree = ""; }; + 485276521376992B007CDA4F /* db_link_button_down.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = db_link_button_down.png; sourceTree = ""; }; + 485276531376992B007CDA4F /* db_link_header.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = db_link_header.png; sourceTree = ""; }; + 485276541376992B007CDA4F /* db_link_header@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "db_link_header@2x.png"; sourceTree = ""; }; + 485276551376992B007CDA4F /* db_logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = db_logo.png; sourceTree = ""; }; + 485276561376992B007CDA4F /* db_logo@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "db_logo@2x.png"; sourceTree = ""; }; 8D1107310486CEB800E47090 /* MyKeePass-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "MyKeePass-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; F716539D115DBD090017DC12 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; F78F24AE10E4740C0073C213 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; @@ -259,6 +388,7 @@ F7DDD0D3115DB58000FD4EE3 /* SystemConfiguration.framework in Frameworks */, F7DDD0D7115DB58500FD4EE3 /* MessageUI.framework in Frameworks */, F7DDD100115DB6B000FD4EE3 /* libKeePass2.a in Frameworks */, + 485275FE137698B0007CDA4F /* Security.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -313,6 +443,7 @@ 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + 485275FD137698B0007CDA4F /* Security.framework */, F7DA414A10CB60FC00F1D072 /* QuartzCore.framework */, 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, 1D30AB110D05D00D00671497 /* Foundation.framework */, @@ -322,6 +453,128 @@ name = Frameworks; sourceTree = ""; }; + 485276001376992B007CDA4F /* DropboxSDK */ = { + isa = PBXGroup; + children = ( + 485276011376992B007CDA4F /* DBAccountInfo.h */, + 485276021376992B007CDA4F /* DBAccountInfo.m */, + 485276031376992B007CDA4F /* DBCreateAccountController.h */, + 485276041376992B007CDA4F /* DBCreateAccountController.m */, + 485276051376992B007CDA4F /* DBError.h */, + 485276061376992B007CDA4F /* DBError.m */, + 485276071376992B007CDA4F /* DBLoadingView.h */, + 485276081376992B007CDA4F /* DBLoadingView.m */, + 485276091376992B007CDA4F /* DBLoginController.h */, + 4852760A1376992B007CDA4F /* DBLoginController.m */, + 4852760B1376992B007CDA4F /* DBMetadata.h */, + 4852760C1376992B007CDA4F /* DBMetadata.m */, + 4852760D1376992B007CDA4F /* DBQuota.h */, + 4852760E1376992B007CDA4F /* DBQuota.m */, + 4852760F1376992B007CDA4F /* DBRequest.h */, + 485276101376992B007CDA4F /* DBRequest.m */, + 485276111376992B007CDA4F /* DBRestClient.h */, + 485276121376992B007CDA4F /* DBRestClient.m */, + 485276131376992B007CDA4F /* DBSession.h */, + 485276141376992B007CDA4F /* DBSession.m */, + 485276151376992B007CDA4F /* DropboxSDK.h */, + 485276161376992B007CDA4F /* JSON */, + 485276241376992B007CDA4F /* MPOAuth */, + 485276491376992B007CDA4F /* NSString+Dropbox.h */, + 4852764A1376992B007CDA4F /* NSString+Dropbox.m */, + 4852764B1376992B007CDA4F /* Resources */, + ); + name = DropboxSDK; + path = ThirdParty/DropboxSDK; + sourceTree = ""; + }; + 485276161376992B007CDA4F /* JSON */ = { + isa = PBXGroup; + children = ( + 485276171376992B007CDA4F /* JSON.h */, + 485276181376992B007CDA4F /* NSObject+SBJSON.h */, + 485276191376992B007CDA4F /* NSObject+SBJSON.m */, + 4852761A1376992B007CDA4F /* NSString+SBJSON.h */, + 4852761B1376992B007CDA4F /* NSString+SBJSON.m */, + 4852761C1376992B007CDA4F /* SBJSON.h */, + 4852761D1376992B007CDA4F /* SBJSON.m */, + 4852761E1376992B007CDA4F /* SBJsonBase.h */, + 4852761F1376992B007CDA4F /* SBJsonBase.m */, + 485276201376992B007CDA4F /* SBJsonParser.h */, + 485276211376992B007CDA4F /* SBJsonParser.m */, + 485276221376992B007CDA4F /* SBJsonWriter.h */, + 485276231376992B007CDA4F /* SBJsonWriter.m */, + ); + path = JSON; + sourceTree = ""; + }; + 485276241376992B007CDA4F /* MPOAuth */ = { + isa = PBXGroup; + children = ( + 485276251376992B007CDA4F /* Crypto */, + 485276281376992B007CDA4F /* MPDebug.h */, + 485276291376992B007CDA4F /* MPOAuth.h */, + 4852762A1376992B007CDA4F /* MPOAuthAPI.h */, + 4852762B1376992B007CDA4F /* MPOAuthAPI.m */, + 4852762C1376992B007CDA4F /* MPOAuthAPIRequestLoader.h */, + 4852762D1376992B007CDA4F /* MPOAuthAPIRequestLoader.m */, + 4852762E1376992B007CDA4F /* MPOAuthAuthenticationMethod.h */, + 4852762F1376992B007CDA4F /* MPOAuthAuthenticationMethod.m */, + 485276301376992B007CDA4F /* MPOAuthAuthenticationMethodOAuth.h */, + 485276311376992B007CDA4F /* MPOAuthAuthenticationMethodOAuth.m */, + 485276321376992B007CDA4F /* MPOAuthConnection.h */, + 485276331376992B007CDA4F /* MPOAuthConnection.m */, + 485276341376992B007CDA4F /* MPOAuthCredentiaIConcreteStore+KeychainAdditionsMac.m */, + 485276351376992B007CDA4F /* MPOAuthCredentialConcreteStore+KeychainAdditions.h */, + 485276361376992B007CDA4F /* MPOAuthCredentialConcreteStore+KeychainAdditionsiPhone.m */, + 485276371376992B007CDA4F /* MPOAuthCredentialConcreteStore.h */, + 485276381376992B007CDA4F /* MPOAuthCredentialConcreteStore.m */, + 485276391376992B007CDA4F /* MPOAuthCredentialStore.h */, + 4852763A1376992B007CDA4F /* MPOAuthParameterFactory.h */, + 4852763B1376992B007CDA4F /* MPOAuthSignatureParameter.h */, + 4852763C1376992B007CDA4F /* MPOAuthSignatureParameter.m */, + 4852763D1376992B007CDA4F /* MPOAuthURLRequest.h */, + 4852763E1376992B007CDA4F /* MPOAuthURLRequest.m */, + 4852763F1376992B007CDA4F /* MPOAuthURLResponse.h */, + 485276401376992B007CDA4F /* MPOAuthURLResponse.m */, + 485276411376992B007CDA4F /* MPURLRequestParameter.h */, + 485276421376992B007CDA4F /* MPURLRequestParameter.m */, + 485276431376992B007CDA4F /* NSString+URLEscapingAdditions.h */, + 485276441376992B007CDA4F /* NSString+URLEscapingAdditions.m */, + 485276451376992B007CDA4F /* NSURL+MPURLParameterAdditions.h */, + 485276461376992B007CDA4F /* NSURL+MPURLParameterAdditions.m */, + 485276471376992B007CDA4F /* NSURLResponse+Encoding.h */, + 485276481376992B007CDA4F /* NSURLResponse+Encoding.m */, + ); + path = MPOAuth; + sourceTree = ""; + }; + 485276251376992B007CDA4F /* Crypto */ = { + isa = PBXGroup; + children = ( + 485276261376992B007CDA4F /* Base64Transcoder.c */, + 485276271376992B007CDA4F /* Base64Transcoder.h */, + ); + path = Crypto; + sourceTree = ""; + }; + 4852764B1376992B007CDA4F /* Resources */ = { + isa = PBXGroup; + children = ( + 4852764C1376992B007CDA4F /* db_background.png */, + 4852764D1376992B007CDA4F /* db_create_account.png */, + 4852764E1376992B007CDA4F /* db_create_account@2x.png */, + 4852764F1376992B007CDA4F /* db_create_account_button.png */, + 485276501376992B007CDA4F /* db_create_account_button_down.png */, + 485276511376992B007CDA4F /* db_link_button.png */, + 485276521376992B007CDA4F /* db_link_button_down.png */, + 485276531376992B007CDA4F /* db_link_header.png */, + 485276541376992B007CDA4F /* db_link_header@2x.png */, + 485276551376992B007CDA4F /* db_logo.png */, + 485276561376992B007CDA4F /* db_logo@2x.png */, + ); + path = Resources; + sourceTree = ""; + }; F7DDCFD6115DB11B00FD4EE3 /* Classes */ = { isa = PBXGroup; children = ( @@ -329,6 +582,7 @@ F7DDD013115DB13500FD4EE3 /* UI */, F7DDD04C115DB13500FD4EE3 /* MyKeePassAppDelegate.h */, F7DDD04D115DB13500FD4EE3 /* MyKeePassAppDelegate.m */, + 485275FF1376990C007CDA4F /* DropboxAPIKeys.h */, ); path = Classes; sourceTree = ""; @@ -336,6 +590,7 @@ F7DDCFE0115DB13500FD4EE3 /* ThirdParty */ = { isa = PBXGroup; children = ( + 485276001376992B007CDA4F /* DropboxSDK */, F7DDD00A115DB13500FD4EE3 /* Tapku */, F7DDCFFA115DB13500FD4EE3 /* ASIHTTPRequest */, F7DDCFE1115DB13500FD4EE3 /* CocoaHttpServer */, @@ -636,6 +891,17 @@ F7DDD117115DB80A00FD4EE3 /* Icon-Small.png in Resources */, F7DDD118115DB80A00FD4EE3 /* Icon.png in Resources */, F716539E115DBD090017DC12 /* Default.png in Resources */, + 485276781376992B007CDA4F /* db_background.png in Resources */, + 485276791376992B007CDA4F /* db_create_account.png in Resources */, + 4852767A1376992B007CDA4F /* db_create_account@2x.png in Resources */, + 4852767B1376992B007CDA4F /* db_create_account_button.png in Resources */, + 4852767C1376992B007CDA4F /* db_create_account_button_down.png in Resources */, + 4852767D1376992B007CDA4F /* db_link_button.png in Resources */, + 4852767E1376992B007CDA4F /* db_link_button_down.png in Resources */, + 4852767F1376992B007CDA4F /* db_link_header.png in Resources */, + 485276801376992B007CDA4F /* db_link_header@2x.png in Resources */, + 485276811376992B007CDA4F /* db_logo.png in Resources */, + 485276821376992B007CDA4F /* db_logo@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -696,6 +962,39 @@ F7DDD07D115DB13500FD4EE3 /* AboutViewController.m in Sources */, F7DDD07E115DB13500FD4EE3 /* MyKeePassAppDelegate.m in Sources */, F7DDD099115DB16800FD4EE3 /* main.m in Sources */, + 485276571376992B007CDA4F /* DBAccountInfo.m in Sources */, + 485276581376992B007CDA4F /* DBCreateAccountController.m in Sources */, + 485276591376992B007CDA4F /* DBError.m in Sources */, + 4852765A1376992B007CDA4F /* DBLoadingView.m in Sources */, + 4852765B1376992B007CDA4F /* DBLoginController.m in Sources */, + 4852765C1376992B007CDA4F /* DBMetadata.m in Sources */, + 4852765D1376992B007CDA4F /* DBQuota.m in Sources */, + 4852765E1376992B007CDA4F /* DBRequest.m in Sources */, + 4852765F1376992B007CDA4F /* DBRestClient.m in Sources */, + 485276601376992B007CDA4F /* DBSession.m in Sources */, + 485276611376992B007CDA4F /* NSObject+SBJSON.m in Sources */, + 485276621376992B007CDA4F /* NSString+SBJSON.m in Sources */, + 485276631376992B007CDA4F /* SBJSON.m in Sources */, + 485276641376992B007CDA4F /* SBJsonBase.m in Sources */, + 485276651376992B007CDA4F /* SBJsonParser.m in Sources */, + 485276661376992B007CDA4F /* SBJsonWriter.m in Sources */, + 485276671376992B007CDA4F /* Base64Transcoder.c in Sources */, + 485276681376992B007CDA4F /* MPOAuthAPI.m in Sources */, + 485276691376992B007CDA4F /* MPOAuthAPIRequestLoader.m in Sources */, + 4852766A1376992B007CDA4F /* MPOAuthAuthenticationMethod.m in Sources */, + 4852766B1376992B007CDA4F /* MPOAuthAuthenticationMethodOAuth.m in Sources */, + 4852766C1376992B007CDA4F /* MPOAuthConnection.m in Sources */, + 4852766D1376992B007CDA4F /* MPOAuthCredentiaIConcreteStore+KeychainAdditionsMac.m in Sources */, + 4852766E1376992B007CDA4F /* MPOAuthCredentialConcreteStore+KeychainAdditionsiPhone.m in Sources */, + 4852766F1376992B007CDA4F /* MPOAuthCredentialConcreteStore.m in Sources */, + 485276701376992B007CDA4F /* MPOAuthSignatureParameter.m in Sources */, + 485276711376992B007CDA4F /* MPOAuthURLRequest.m in Sources */, + 485276721376992B007CDA4F /* MPOAuthURLResponse.m in Sources */, + 485276731376992B007CDA4F /* MPURLRequestParameter.m in Sources */, + 485276741376992B007CDA4F /* NSString+URLEscapingAdditions.m in Sources */, + 485276751376992B007CDA4F /* NSURL+MPURLParameterAdditions.m in Sources */, + 485276761376992B007CDA4F /* NSURLResponse+Encoding.m in Sources */, + 485276771376992B007CDA4F /* NSString+Dropbox.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 4c284c60d20a280e4d2e651e0714ddbb25edd511 Mon Sep 17 00:00:00 2001 From: jrroca Date: Sun, 8 May 2011 13:24:24 +0200 Subject: [PATCH 3/6] Added Dropbox URLs support for remote hosts. --- Classes/UI/FileManager.m | 17 +- Classes/UI/Main/DropboxFileViewController.h | 31 ++ Classes/UI/Main/DropboxFileViewController.m | 329 ++++++++++++++++++++ Classes/UI/Main/FileViewController.m | 52 +++- DropboxFileView.xib | 272 ++++++++++++++++ MyKeePass.xcodeproj/project.pbxproj | 10 + 6 files changed, 700 insertions(+), 11 deletions(-) create mode 100644 Classes/UI/Main/DropboxFileViewController.h create mode 100644 Classes/UI/Main/DropboxFileViewController.m create mode 100644 DropboxFileView.xib diff --git a/Classes/UI/FileManager.m b/Classes/UI/FileManager.m index eb92151..091df72 100644 --- a/Classes/UI/FileManager.m +++ b/Classes/UI/FileManager.m @@ -15,9 +15,12 @@ #import "ASIHTTPRequest.h" #import "MyKeePassAppDelegate.h" #import "ActivityView.h" +#import "DropboxSDK.h" @interface FileManager(PrivateMethods) -(id) readFileHelp:(NSString *) fileName withPassword:(NSString *)password; +-(BOOL)bIsDropBoxFileName:(NSString *)filename; +-(BOOL)bIsDropBoxURL:(NSString *)url; @end @@ -41,7 +44,7 @@ @implementation FileManager +(void)initialize{ if ( self == [FileManager class] ) { -#if TARGET_IPHONE_SIMULATOR +#if TARGET_IPHONE_SIMULATOR_2 DATA_DIR = @"/Volumes/Users/qiang/Desktop/"; DOWNLOAD_DIR = DATA_DIR; #else @@ -240,4 +243,16 @@ +(void)newKdb3File:(NSString *)filename withPassword:(NSString *)password{ } } +-(BOOL)bIsDropBoxFileName:(NSString *)filename { + NSString *url = [self getURLForRemoteFile:filename]; + return [self bIsDropBoxURL:url]; +} + +-(BOOL)bIsDropBoxURL:(NSString *) url { + if ([url hasPrefix:@"dropbox://"]){ + return YES; + } + return NO; +} + @end diff --git a/Classes/UI/Main/DropboxFileViewController.h b/Classes/UI/Main/DropboxFileViewController.h new file mode 100644 index 0000000..8cf906b --- /dev/null +++ b/Classes/UI/Main/DropboxFileViewController.h @@ -0,0 +1,31 @@ +// +// DropboxFileViewController.h +// MyKeePass +// +// Created by Jose Ramon Roca on 07/05/11. +// Copyright 2011 -. All rights reserved. +// + +#import +#import "DropboxSDK.h" + +@class DBRestClient; + +@interface DropboxFileViewController : UIViewController { + UITableView *tableView; + DBRestClient *restClient; + NSMutableArray *dropBoxContents; + UIActivityIndicatorView* activityIndicator; + BOOL working; + NSString *currentPath; + DropboxFileViewController *dropboxFileSubViewController; +} + +@property (nonatomic, retain) IBOutlet UITableView *tableView; +@property (nonatomic, retain) DBRestClient *restClient; +@property (nonatomic, retain) NSMutableArray *dropBoxContents; +@property (nonatomic, retain) IBOutlet UIActivityIndicatorView* activityIndicator; +@property (nonatomic, retain) NSString *currentPath; +@property (nonatomic, retain) DropboxFileViewController *dropboxFileSubViewController; + +@end diff --git a/Classes/UI/Main/DropboxFileViewController.m b/Classes/UI/Main/DropboxFileViewController.m new file mode 100644 index 0000000..29410c1 --- /dev/null +++ b/Classes/UI/Main/DropboxFileViewController.m @@ -0,0 +1,329 @@ +// +// DropboxFileViewController.m +// MyKeePass +// +// Created by Jose Ramon Roca on 07/05/11. +// Copyright 2011 -. All rights reserved. +// + +#import "DropboxFileViewController.h" +#import "DropboxSDK.h" +#import "FileViewController.h" +#import "MyKeePassAppDelegate.h" + +@interface DropboxFileViewController(PrivateMethods) +-(IBAction)cancelClicked:(id)sender; +-(void)updateTable; +- (void)setWorking:(BOOL)isWorking; +-(void)configureCell:(UITableViewCell*) cell indexPath:(NSIndexPath *)indexPath; +@end + + +@implementation DropboxFileViewController + +#define TYPE_DIR "D" +#define TYPE_FILE "F" + +#define DROPBOX_ARRAY_TYPE 0 +#define DROPBOX_ARRAY_PATH 1 + +@synthesize tableView; +@synthesize restClient; +@synthesize dropBoxContents; +@synthesize activityIndicator; +@synthesize currentPath; +@synthesize dropboxFileSubViewController; + +- (void)dealloc +{ + [restClient release]; + [dropBoxContents release]; + [activityIndicator release]; + [currentPath release]; + [dropboxFileSubViewController release]; + [super dealloc]; +} + +- (void)didReceiveMemoryWarning +{ + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +#pragma mark - View lifecycle + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + //DBLoginController* controller = [[DBLoginController new] autorelease]; + //[controller presentFromController:self]; + + // Uncomment the following line to preserve selection between presentations. + // self.clearsSelectionOnViewWillAppear = NO; + + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. + // self.navigationItem.rightBarButtonItem = self.editButtonItem; + self.navigationItem.title = NSLocalizedString(@"New Dropbox File", @"New Dropbox File"); + + UIBarButtonItem * cancel = [[UIBarButtonItem alloc]initWithTitle:NSLocalizedString(@"Cancel", @"Cancel") + style:UIBarButtonItemStylePlain target:self action:@selector(cancelClicked:)]; + self.navigationItem.rightBarButtonItem = cancel; + [cancel release]; + + /* + UIBarButtonItem * done = [[UIBarButtonItem alloc]initWithTitle:NSLocalizedString(@"Done", @"Done") + style:UIBarButtonItemStyleDone target:self action:@selector(doneClicked:)]; + self.navigationItem.rightBarButtonItem = done; + [done release]; + */ + dropBoxContents = [NSMutableArray new]; + if (currentPath == nil) { + currentPath = @"/"; + } +} + +- (void)viewDidUnload +{ + [super viewDidUnload]; + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; +} + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + if (![[DBSession sharedSession] isLinked]) { + DBLoginController* controller = [[DBLoginController new] autorelease]; + controller.delegate = self; + [controller presentFromController:self]; + } + else + { + [self updateTable]; + } +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; +} + +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + // Return YES for supported orientations + return (interfaceOrientation == UIInterfaceOrientationPortrait); +} + +#pragma mark - Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + // Return the number of sections. + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + // Return the number of rows in the section. + return [dropBoxContents count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + static NSString *CellIdentifier = @"Cell"; + + UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; + } + + // Configure the cell... + [self configureCell:cell indexPath:indexPath]; + return cell; +} + +/* +// Override to support conditional editing of the table view. +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath +{ + // Return NO if you do not want the specified item to be editable. + return YES; +} +*/ + +/* +// Override to support editing the table view. +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (editingStyle == UITableViewCellEditingStyleDelete) { + // Delete the row from the data source + [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; + } + else if (editingStyle == UITableViewCellEditingStyleInsert) { + // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view + } +} +*/ + +/* +// Override to support rearranging the table view. +- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath +{ +} +*/ + +/* +// Override to support conditional rearranging of the table view. +- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath +{ + // Return NO if you do not want the item to be re-orderable. + return YES; +} +*/ + +#pragma mark - Table view delegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + + NSMutableArray *aAux = [dropBoxContents objectAtIndex:indexPath.row]; + if ([[aAux objectAtIndex:DROPBOX_ARRAY_TYPE] isEqualToString:@"D"]) { + if (dropboxFileSubViewController == nil) { + DropboxFileViewController *dropBoxViewController = [[DropboxFileViewController alloc] initWithNibName:@"DropboxFileView" bundle:nil]; + self.dropboxFileSubViewController = dropBoxViewController; + [dropBoxViewController release]; + } + dropboxFileSubViewController.currentPath = [aAux objectAtIndex:DROPBOX_ARRAY_PATH]; + [[self navigationController] pushViewController:self.dropboxFileSubViewController animated:YES]; + } + else { + NSString *path = [aAux objectAtIndex:DROPBOX_ARRAY_PATH]; + NSString *url = [NSString stringWithFormat:@"dropbox://%@", [path substringWithRange:NSMakeRange(1, [path length]-1)]]; + NSString *name = [[aAux objectAtIndex:DROPBOX_ARRAY_PATH] lastPathComponent]; + if([[MyKeePassAppDelegate delegate]._fileManager getURLForRemoteFile:name]) { + UIAlertView * alert = [[UIAlertView alloc]initWithTitle:nil + message:NSLocalizedString(@"A same name already exists", @"Name already exisits") delegate:nil + cancelButtonTitle:NSLocalizedString(@"OK", @"OK") + otherButtonTitles:nil]; + [alert show]; + [alert release]; + } + else { + [[MyKeePassAppDelegate delegate]._fileManager addRemoteFile:name Url:url]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"DismissModalViewOK" object:self]; + } + } +} + +#pragma mark DBLoginControllerDelegate methods + +- (void)loginControllerDidLogin:(DBLoginController*)controller { + [self updateTable]; +} + +- (void)loginControllerDidCancel:(DBLoginController*)controller { + [self cancelClicked:nil]; +} + +#pragma mark DBRestClient + +- (DBRestClient*)restClient { + if (!restClient) { + restClient = + [[DBRestClient alloc] initWithSession:[DBSession sharedSession]]; + restClient.delegate = self; + } + return restClient; +} + + +#pragma mark DBRestClientDelegate methods + +- (void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata { + + NSArray *validExtensions = [NSArray arrayWithObjects:@"kdb", @"kdbx", nil]; + NSMutableArray *aAux; + [dropBoxContents release]; + dropBoxContents = [NSMutableArray new]; + + for (DBMetadata *child in metadata.contents) { + if (child.isDirectory) { + aAux = [[NSMutableArray alloc] init]; + [aAux autorelease]; + [aAux insertObject:@"D" atIndex:DROPBOX_ARRAY_TYPE]; + [aAux insertObject:child.path atIndex:DROPBOX_ARRAY_PATH]; + [dropBoxContents addObject:aAux]; + } + else { + NSString* extension = [[child.path pathExtension] lowercaseString]; + if ([validExtensions indexOfObject:extension] != NSNotFound) { + aAux = [[NSMutableArray alloc] init]; + [aAux autorelease]; + [aAux insertObject:@"F" atIndex:DROPBOX_ARRAY_TYPE]; + [aAux insertObject:child.path atIndex:DROPBOX_ARRAY_PATH]; + [dropBoxContents addObject:aAux]; + } + } + } + [self setWorking:NO]; + [[self tableView] reloadData]; +} + +- (void)restClient:(DBRestClient*)client metadataUnchangedAtPath:(NSString*)path { + + NSLog(@"Metadata unchanged!"); +} + +- (void)restClient:(DBRestClient*)client loadMetadataFailedWithError:(NSError*)error { +#warning Need to handle errors. + NSLog(@"Error loading metadata: %@", error); +} + +#pragma mark - Private Methods + +-(IBAction)cancelClicked:(id)sender{ + [[NSNotificationCenter defaultCenter] postNotificationName:@"DismissModalViewCancel" object:self]; +} + +-(void)updateTable { + [self setWorking:YES]; + [[self restClient] loadMetadata:currentPath]; +} + +-(void)setWorking:(BOOL)isWorking { + if (working == isWorking) return; + working = isWorking; + + if (working) { + [activityIndicator startAnimating]; + } else { + [activityIndicator stopAnimating]; + } +} + +-(void)configureCell:(UITableViewCell*) cell indexPath:(NSIndexPath *)indexPath { + NSMutableArray *aAux = [dropBoxContents objectAtIndex:indexPath.row]; + + cell.textLabel.text = [[aAux objectAtIndex:DROPBOX_ARRAY_PATH] lastPathComponent]; + if ([[aAux objectAtIndex:DROPBOX_ARRAY_TYPE] isEqualToString:@"D"]) { + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + } + else { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } +} + +@end diff --git a/Classes/UI/Main/FileViewController.m b/Classes/UI/Main/FileViewController.m index a3ea3f5..d6fd4ed 100644 --- a/Classes/UI/Main/FileViewController.m +++ b/Classes/UI/Main/FileViewController.m @@ -16,6 +16,8 @@ #import "RenameFileViewController.h" #import "RenameRemoteFileViewController.h" #import "FileUploadViewController.h" +#import "DropboxSDK.h" +#import "DropboxFileViewController.h" #define KDB1_SUFFIX ".kdb" #define KDB2_SUFFIX ".kdbx" @@ -23,10 +25,12 @@ #define NEW_FILE_BUTTON 0 #define IMPORT_DESKTOP_BUTTON 1 #define IMPORT_WEBSERVER_BUTTON 2 +#define IMPORT_DROPBOX_BUTTON 3 @interface FileViewController(PrivateMethods) -(void)newFile; -(void)downloadFile; +-(void)dropboxFile; -(void)uploadFile; @end @@ -78,14 +82,18 @@ - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexP [nav release]; }else{ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissModalViewCancel:) name:@"DismissModalViewCancel" object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissModalViewOK:) name:@"DismissModalViewOK" object:nil]; - RenameRemoteFileViewController * renameFile = [[RenameRemoteFileViewController alloc]initWithStyle:UITableViewStyleGrouped]; - renameFile._name = [_remoteFiles objectAtIndex:indexPath.row - [_files count]]; - renameFile._url = [[MyKeePassAppDelegate delegate]._fileManager getURLForRemoteFile:renameFile._name]; - UINavigationController * nav = [[UINavigationController alloc]initWithRootViewController:renameFile]; - [self presentModalViewController:nav animated:YES]; - [renameFile release]; - [nav release]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissModalViewOK:) name:@"DismissModalViewOK" object:nil]; + + NSString *filename = [_remoteFiles objectAtIndex:indexPath.row - [_files count]]; + if (![[MyKeePassAppDelegate delegate]._fileManager bIsDropBoxFileName:filename]) { + RenameRemoteFileViewController * renameFile = [[RenameRemoteFileViewController alloc]initWithStyle:UITableViewStyleGrouped]; + renameFile._name = [_remoteFiles objectAtIndex:indexPath.row - [_files count]]; + renameFile._url = [[MyKeePassAppDelegate delegate]._fileManager getURLForRemoteFile:renameFile._name]; + UINavigationController * nav = [[UINavigationController alloc]initWithRootViewController:renameFile]; + [self presentModalViewController:nav animated:YES]; + [renameFile release]; + [nav release]; + } } } @@ -118,7 +126,13 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N //cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; NSString * filename = [_remoteFiles objectAtIndex:indexPath.row-[_files count]]; UIImageView * iv = cell.imageView; - iv.image = [UIImage imageNamed:@"http.png"]; + if ([[MyKeePassAppDelegate delegate]._fileManager bIsDropBoxFileName:filename]) { + iv.image = [UIImage imageNamed:@"dropbox.png"]; + cell.editingAccessoryType =UITableViewCellEditingStyleNone; + } + else { + iv.image = [UIImage imageNamed:@"http.png"]; + } cell.textLabel.text = filename; } return cell; @@ -184,6 +198,16 @@ -(void)downloadFile{ [nav release]; } +-(void)dropboxFile{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissModalViewCancel:) name:@"DismissModalViewCancel" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissModalViewOK:) name:@"DismissModalViewOK" object:nil]; + DropboxFileViewController *aDropboxFileViewController = [[DropboxFileViewController alloc]initWithNibName:@"DropboxFileView" bundle:nil]; + UINavigationController * nav = [[UINavigationController alloc]initWithRootViewController:aDropboxFileViewController]; + [self presentModalViewController:nav animated:YES]; + [aDropboxFileViewController release]; + [nav release]; +} + -(void)dismissModalViewCancel:(NSNotification *)notification{ [self dismissModalViewControllerAnimated:YES]; [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -210,6 +234,9 @@ -(void)dismissModalViewOK:(NSNotification *)notification{ }else if([[notification object] isKindOfClass:[FileUploadViewController class]]){ [[MyKeePassAppDelegate delegate]._fileManager getKDBFiles:_files]; [self.tableView reloadData]; + }else if ([[notification object] isKindOfClass:[DropboxFileViewController class]]){ + [[MyKeePassAppDelegate delegate]._fileManager getRemoteFiles:_remoteFiles]; + [self.tableView reloadData]; } [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -220,7 +247,8 @@ -(IBAction)addFile:(id)sender { cancelButtonTitle:NSLocalizedString(@"Cancel", @"Cancel") destructiveButtonTitle:nil otherButtonTitles:NSLocalizedString(@"New KDB 1.0 File", @"New KDB 1.0 File"), NSLocalizedString(@"Upload From Desktop", @"Upload From Desktop"), - NSLocalizedString(@"Download From WWW", @"Download From WWW"), nil]; + NSLocalizedString(@"Download From WWW", @"Download From WWW"), + NSLocalizedString(@"Download From Dropbox", @"Download From Dropbox"), nil]; [as showInView:[MyKeePassAppDelegate getWindow]]; } @@ -238,6 +266,10 @@ - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger [self downloadFile]; break; } + case IMPORT_DROPBOX_BUTTON:{ + [self dropboxFile]; + break; + } } } diff --git a/DropboxFileView.xib b/DropboxFileView.xib new file mode 100644 index 0000000..9b37866 --- /dev/null +++ b/DropboxFileView.xib @@ -0,0 +1,272 @@ + + + + 1056 + 10J567 + 1306 + 1038.35 + 462.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 301 + + + YES + IBUITableView + IBUIActivityIndicatorView + IBUIView + IBProxyObject + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + + + YES + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 292 + + YES + + + 274 + {320, 460} + + + + + 3 + MQA + + NO + YES + NO + + IBCocoaTouchFramework + NO + 1 + 0 + YES + 44 + 22 + 22 + + + + -2147483356 + {{141, 212}, {37, 37}} + + + + NO + IBCocoaTouchFramework + 0 + + + {320, 460} + + + + + 3 + MQA + + 2 + + + IBCocoaTouchFramework + + + + + YES + + + dataSource + + + + 6 + + + + delegate + + + + 7 + + + + activityIndicator + + + + 9 + + + + view + + + + 11 + + + + tableView + + + + 12 + + + + + YES + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 10 + + + YES + + + + + + + 4 + + + + + 8 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 10.IBPluginDependency + 4.IBEditorWindowLastContentRect + 4.IBPluginDependency + 8.IBPluginDependency + + + YES + DropboxFileViewController + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{329, 504}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + + + + + YES + + + + + 12 + + + + YES + + DropboxFileViewController + UIViewController + + YES + + YES + activityIndicator + tableView + + + YES + UIActivityIndicatorView + UITableView + + + + YES + + YES + activityIndicator + tableView + + + YES + + activityIndicator + UIActivityIndicatorView + + + tableView + UITableView + + + + + IBProjectSource + ./Classes/DropboxFileViewController.h + + + + + 0 + IBCocoaTouchFramework + + com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 + + + YES + 3 + 301 + + diff --git a/MyKeePass.xcodeproj/project.pbxproj b/MyKeePass.xcodeproj/project.pbxproj index 61dc176..836f95c 100755 --- a/MyKeePass.xcodeproj/project.pbxproj +++ b/MyKeePass.xcodeproj/project.pbxproj @@ -55,6 +55,8 @@ 485276801376992B007CDA4F /* db_link_header@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 485276541376992B007CDA4F /* db_link_header@2x.png */; }; 485276811376992B007CDA4F /* db_logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 485276551376992B007CDA4F /* db_logo.png */; }; 485276821376992B007CDA4F /* db_logo@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 485276561376992B007CDA4F /* db_logo@2x.png */; }; + 4852769913769EBA007CDA4F /* DropboxFileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4852769813769EBA007CDA4F /* DropboxFileViewController.m */; }; + 4852769B13769EDA007CDA4F /* DropboxFileView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4852769A13769EDA007CDA4F /* DropboxFileView.xib */; }; F716539E115DBD090017DC12 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = F716539D115DBD090017DC12 /* Default.png */; }; F78F24AF10E4740C0073C213 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F78F24AE10E4740C0073C213 /* CFNetwork.framework */; }; F7DA414B10CB60FC00F1D072 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7DA414A10CB60FC00F1D072 /* QuartzCore.framework */; }; @@ -245,6 +247,9 @@ 485276541376992B007CDA4F /* db_link_header@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "db_link_header@2x.png"; sourceTree = ""; }; 485276551376992B007CDA4F /* db_logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = db_logo.png; sourceTree = ""; }; 485276561376992B007CDA4F /* db_logo@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "db_logo@2x.png"; sourceTree = ""; }; + 4852769713769EBA007CDA4F /* DropboxFileViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropboxFileViewController.h; sourceTree = ""; }; + 4852769813769EBA007CDA4F /* DropboxFileViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DropboxFileViewController.m; sourceTree = ""; }; + 4852769A13769EDA007CDA4F /* DropboxFileView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DropboxFileView.xib; sourceTree = ""; }; 8D1107310486CEB800E47090 /* MyKeePass-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "MyKeePass-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; F716539D115DBD090017DC12 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; F78F24AE10E4740C0073C213 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; @@ -434,6 +439,7 @@ 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( + 4852769A13769EDA007CDA4F /* DropboxFileView.xib */, F7DDD09A115DB18200FD4EE3 /* MainWindow.xib */, 8D1107310486CEB800E47090 /* MyKeePass-Info.plist */, ); @@ -770,6 +776,8 @@ F7DDD043115DB13500FD4EE3 /* FileUploadViewController.m */, F7DDD044115DB13500FD4EE3 /* FileManagerOperation.h */, F7DDD045115DB13500FD4EE3 /* FileManagerOperation.m */, + 4852769713769EBA007CDA4F /* DropboxFileViewController.h */, + 4852769813769EBA007CDA4F /* DropboxFileViewController.m */, ); path = Main; sourceTree = ""; @@ -902,6 +910,7 @@ 485276801376992B007CDA4F /* db_link_header@2x.png in Resources */, 485276811376992B007CDA4F /* db_logo.png in Resources */, 485276821376992B007CDA4F /* db_logo@2x.png in Resources */, + 4852769B13769EDA007CDA4F /* DropboxFileView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -995,6 +1004,7 @@ 485276751376992B007CDA4F /* NSURL+MPURLParameterAdditions.m in Sources */, 485276761376992B007CDA4F /* NSURLResponse+Encoding.m in Sources */, 485276771376992B007CDA4F /* NSString+Dropbox.m in Sources */, + 4852769913769EBA007CDA4F /* DropboxFileViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 3393efa21c46b64aff9ee3a10875e416ba49e211 Mon Sep 17 00:00:00 2001 From: jrroca Date: Sun, 8 May 2011 22:11:46 +0200 Subject: [PATCH 4/6] Inicial version with dropbox download working. It needs more error handling. --- Classes/UI/FileManager.h | 18 +++- Classes/UI/FileManager.m | 129 +++++++++++++++++------ Classes/UI/Main/FileManagerOperation.h | 1 + Classes/UI/Main/FileManagerOperation.m | 12 ++- Classes/UI/Main/PasswordViewController.m | 25 ++++- 5 files changed, 149 insertions(+), 36 deletions(-) diff --git a/Classes/UI/FileManager.h b/Classes/UI/FileManager.h index 205b220..b39a6f9 100644 --- a/Classes/UI/FileManager.h +++ b/Classes/UI/FileManager.h @@ -8,29 +8,41 @@ #import #import +#import "DropboxSDK.h" #define KDB_VERSION1 1 #define KDB_VERSION2 2 -@interface FileManager : NSObject { +@class DBRestClient; +@class PasswordViewController; + +@interface FileManager : NSObject { //e.g. test.kdb; directory information is not included; //if it is a remote file remote<->http://www.exaple.com, _filename=remote NSString * _filename; NSString * _password; + NSString * _cacheFileName; BOOL _dirty; id _kdbReader; BOOL _editable; NSMutableDictionary * _remoteFiles; + DBRestClient *_restClient; + + PasswordViewController *_passwordViewController; } @property(nonatomic, retain) id _kdbReader; @property(nonatomic, readonly) BOOL _editable; @property(nonatomic, retain) NSString * _filename; @property(nonatomic, retain) NSString * _password; +@property(nonatomic, retain) NSString * _cacheFileName; @property(nonatomic, assign) BOOL _dirty; @property(nonatomic, retain) NSMutableDictionary * _remoteFiles; +@property(nonatomic, retain) DBRestClient *_restClient; + +@property(nonatomic, retain) PasswordViewController *_passwordViewController; -(void)getKDBFiles:(NSMutableArray *)list; -(id) readFile:(NSString *) fileName withPassword:(NSString *)password; @@ -56,4 +68,8 @@ +(NSString *)getFullFileName:(NSString *)filename; +(NSString *)dataDir; + +-(BOOL)bIsDropBoxFileName:(NSString *)filename; +-(BOOL)bIsDropBoxURL:(NSString *)url; + @end diff --git a/Classes/UI/FileManager.m b/Classes/UI/FileManager.m index 091df72..840d0f3 100644 --- a/Classes/UI/FileManager.m +++ b/Classes/UI/FileManager.m @@ -19,8 +19,6 @@ @interface FileManager(PrivateMethods) -(id) readFileHelp:(NSString *) fileName withPassword:(NSString *)password; --(BOOL)bIsDropBoxFileName:(NSString *)filename; --(BOOL)bIsDropBoxURL:(NSString *)url; @end @@ -31,6 +29,10 @@ @implementation FileManager @synthesize _password; @synthesize _dirty; @synthesize _remoteFiles; +@synthesize _cacheFileName; +@synthesize _restClient; + +@synthesize _passwordViewController; #define KDB_PATH "Passwords" #define DOWNLOAD_PATH "Download" @@ -122,51 +124,67 @@ +(NSString *)getTempFileNameFromURL:(NSString *)url{ } -(id) readRemoteFile:(NSString *)filename withPassword:(NSString *)password useCached:(BOOL)useCached username:(NSString *)username userpass:(NSString *)userpass domain:(NSString *)domain{ + id tree = nil; self._filename = filename; + _editable = NO; NSString * url = [self getURLForRemoteFile:filename]; if(!url) @throw [NSException exceptionWithName:@"DownloadError" reason:@"DownloadError" userInfo:nil]; NSString * cacheFileName = [FileManager getTempFileNameFromURL:url]; NSString * tmp = [cacheFileName stringByAppendingString:@".tmp"]; + + self._cacheFileName = cacheFileName; NSFileManager * fileManager = [NSFileManager defaultManager]; if([fileManager fileExistsAtPath:cacheFileName]&&useCached){ id tree = [self readFileHelp:cacheFileName withPassword:password]; _editable = NO; + if ([self bIsDropBoxURL:url]) { + [self._passwordViewController performSelector:@selector(fileOperationSuccess) withObject:nil]; + } return tree; } - - ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:url]]; - [request setDownloadDestinationPath:tmp]; - - if([username length]) - [request setUsername:username]; - if([userpass length]) - [request setPassword:userpass]; - if([domain length]) - [request setDomain:domain]; - - [request startSynchronous]; - - int statusCode = [request responseStatusCode]; - - if(statusCode!=200){ - if(statusCode==401){ - @throw [NSException exceptionWithName:@"RemoteAuthenticationError" reason:@"RemoteAuthenticationError" userInfo:nil]; - }else{ - @throw [NSException exceptionWithName:@"DownloadError" reason:@"DownloadError" userInfo:nil]; - } - } - - [[NSFileManager defaultManager] removeItemAtPath:cacheFileName error:nil]; - [[NSFileManager defaultManager] moveItemAtPath:tmp toPath:cacheFileName error:nil]; - id tree = [self readFileHelp:cacheFileName withPassword:password]; - //remote file is not editable, yet - _editable = NO; - return tree; + + if (![self bIsDropBoxURL:url]) { + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:url]]; + [request setDownloadDestinationPath:tmp]; + + if([username length]) + [request setUsername:username]; + if([userpass length]) + [request setPassword:userpass]; + if([domain length]) + [request setDomain:domain]; + + [request startSynchronous]; + + int statusCode = [request responseStatusCode]; + + if(statusCode!=200){ + if(statusCode==401){ + @throw [NSException exceptionWithName:@"RemoteAuthenticationError" reason:@"RemoteAuthenticationError" userInfo:nil]; + }else{ + @throw [NSException exceptionWithName:@"DownloadError" reason:@"DownloadError" userInfo:nil]; + } + } + + [[NSFileManager defaultManager] removeItemAtPath:cacheFileName error:nil]; + [[NSFileManager defaultManager] moveItemAtPath:tmp toPath:cacheFileName error:nil]; + id tree = [self readFileHelp:cacheFileName withPassword:password]; + return tree; + } + else { + if (![[DBSession sharedSession] isLinked]) { + @throw [NSException exceptionWithName:@"RemoteAuthenticationError" reason:@"RemoteAuthenticationError" userInfo:nil]; + } + self._password = password; + NSString *path = [url substringFromIndex:9]; + [[self _restClient] loadFile:path intoPath:tmp]; + } + return tree; } -(void)getKDBFiles:(NSMutableArray *)list{ @@ -255,4 +273,53 @@ -(BOOL)bIsDropBoxURL:(NSString *) url { return NO; } +#pragma mark DBRestClient + +- (DBRestClient*)_restClient { + if (!_restClient) { + _restClient = + [[DBRestClient alloc] initWithSession:[DBSession sharedSession]]; + _restClient.delegate = self; + } + return _restClient; +} + +#pragma mark DBRestClientDelegate methods + +/* + - (void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata { + NSLog(@"loadedMeta"); + } + + - (void)restClient:(DBRestClient*)client metadataUnchangedAtPath:(NSString*)path { + + NSLog(@"Metadata unchanged!"); + } + + - (void)restClient:(DBRestClient*)client loadMetadataFailedWithError:(NSError*)error { + + NSLog(@"Error loading metadata: %@", error); + } + */ + +- (void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath{ + [[NSFileManager defaultManager] removeItemAtPath:self._cacheFileName error:nil]; + [[NSFileManager defaultManager] moveItemAtPath:destPath toPath:self._cacheFileName error:nil]; + @try{ + id tree = [self readFileHelp:self._cacheFileName withPassword:self._password]; + tree = nil; + [self._passwordViewController performSelector:@selector(fileOperationSuccess) withObject:nil]; + }@catch(NSException * exception){ + [self._passwordViewController performSelector:@selector(fileOperationFailureWithException:) withObject:exception]; + } +} +- (void)restClient:(DBRestClient*)client loadProgress:(CGFloat)progress forFile:(NSString*)destPath { + //NSLog(@"Progress"); +} + +- (void)restClient:(DBRestClient*)client loadFileFailedWithError:(NSError*)error { + NSException *exception = [NSException exceptionWithName:@"DownloadErrorWithUserInfo" reason:@"DownloadError" userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[error localizedDescription],@"error", nil]]; + [self._passwordViewController performSelector:@selector(fileOperationFailureWithException:) withObject:exception]; +} + @end diff --git a/Classes/UI/Main/FileManagerOperation.h b/Classes/UI/Main/FileManagerOperation.h index 2141a82..933b2a6 100644 --- a/Classes/UI/Main/FileManagerOperation.h +++ b/Classes/UI/Main/FileManagerOperation.h @@ -41,6 +41,7 @@ -(void)openLocalFile; -(void)openRemoteFile; +-(void)openDropboxRemoteFile; -(void)save; @end diff --git a/Classes/UI/Main/FileManagerOperation.m b/Classes/UI/Main/FileManagerOperation.m index 7ee4b89..6896437 100644 --- a/Classes/UI/Main/FileManagerOperation.m +++ b/Classes/UI/Main/FileManagerOperation.m @@ -54,7 +54,7 @@ -(void)openLocalFile{ -(void)openRemoteFile{ NSAutoreleasePool * pool = nil; @try{ - pool = [[NSAutoreleasePool alloc] init]; + pool = [[NSAutoreleasePool alloc] init]; [[MyKeePassAppDelegate delegate]._fileManager readRemoteFile:_filename withPassword:_password useCached:_useCache username:_username userpass:_userpass domain:_domain]; [_delegate performSelectorOnMainThread:@selector(fileOperationSuccess) withObject:nil waitUntilDone:NO]; }@catch(NSException * exception){ @@ -66,6 +66,16 @@ -(void)openRemoteFile{ } +-(void)openDropboxRemoteFile{ + @try{ + [MyKeePassAppDelegate delegate]._fileManager._passwordViewController = self._delegate; + [[MyKeePassAppDelegate delegate]._fileManager readRemoteFile:_filename withPassword:_password useCached:_useCache username:_username userpass:_userpass domain:_domain]; + //[_delegate performSelectorOnMainThread:@selector(fileOperationSuccess) withObject:nil waitUntilDone:NO]; + }@catch(NSException * exception){ + [_delegate performSelectorOnMainThread:@selector(fileOperationFailureWithException:) withObject:exception waitUntilDone:NO]; + } +} + -(void)save{ NSAutoreleasePool * pool = nil; @try{ diff --git a/Classes/UI/Main/PasswordViewController.m b/Classes/UI/Main/PasswordViewController.m index 52622b7..891051f 100644 --- a/Classes/UI/Main/PasswordViewController.m +++ b/Classes/UI/Main/PasswordViewController.m @@ -11,6 +11,7 @@ #import "MyKeePassAppDelegate.h" #import "ActivityView.h" #import "FileManagerOperation.h" +#import "FileManager.h" @interface PasswordViewController(PrivateMethods) -(void)showError:(NSString *)message; @@ -136,7 +137,18 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return _isRemote?2:1; + if (!_isRemote) { + return 1; + } + else { + if ([[MyKeePassAppDelegate delegate]._fileManager bIsDropBoxFileName:_filename]){ + return 1; + } + else { + return 2; + } + } + return 1; } @@ -193,8 +205,13 @@ -(IBAction)okClicked:(id)sender{ _op._useCache = _switch.on; _op._username = _rusername._field.text; _op._userpass = _rpassword._field.text; - _op._domain = _rdomain._field.text; - [_op performSelectorInBackground:@selector(openRemoteFile) withObject:nil]; + _op._domain = _rdomain._field.text; + if (![[MyKeePassAppDelegate delegate]._fileManager bIsDropBoxFileName:_filename]) { + [_op performSelectorInBackground:@selector(openRemoteFile) withObject:nil]; + } + else { + [_op openDropboxRemoteFile]; + } } }else{ // it is reopen, we verify the password directly NSString * password = _password._field.text; @@ -224,6 +241,8 @@ -(void)fileOperationFailureWithException:(NSException *)exception{ [msg release]; }else if([[exception name] isEqualToString:@"DownloadError"]){ [self showError:NSLocalizedString(@"Cannot download the file", @"Cannot download the file")]; + }else if([[exception name] isEqualToString:@"DownloadErrorWithUserInfo"]){ + [self showError:[NSString stringWithFormat:@"%@.%@", NSLocalizedString(@"Cannot download the file", @"Cannot download the file"),[[exception userInfo] valueForKey:@"error"]]]; }else if ([[exception name] isEqualToString:@"RemoteAuthenticationError"]){ [self showError:NSLocalizedString(@"Server authentication error", @"Server authentication error")]; }else{ From 9d99cc4fe5b867a2a7dcc60c526e728789fd528c Mon Sep 17 00:00:00 2001 From: jrroca Date: Tue, 10 May 2011 21:24:25 +0200 Subject: [PATCH 5/6] Added support to check if file is modified at dropbox when using cache. --- Classes/UI/FileManager.h | 7 +- Classes/UI/FileManager.m | 122 ++++++++++++++++++----- Classes/UI/Main/FileManagerOperation.h | 3 + Classes/UI/Main/FileManagerOperation.m | 5 +- Classes/UI/Main/PasswordViewController.h | 6 +- Classes/UI/Main/PasswordViewController.m | 71 +++++++++++-- 6 files changed, 176 insertions(+), 38 deletions(-) diff --git a/Classes/UI/FileManager.h b/Classes/UI/FileManager.h index b39a6f9..b5b41d6 100644 --- a/Classes/UI/FileManager.h +++ b/Classes/UI/FileManager.h @@ -22,6 +22,7 @@ NSString * _filename; NSString * _password; NSString * _cacheFileName; + NSString * _tmpFileName; BOOL _dirty; id _kdbReader; @@ -38,6 +39,7 @@ @property(nonatomic, retain) NSString * _filename; @property(nonatomic, retain) NSString * _password; @property(nonatomic, retain) NSString * _cacheFileName; +@property(nonatomic, retain) NSString *_tmpFileName; @property(nonatomic, assign) BOOL _dirty; @property(nonatomic, retain) NSMutableDictionary * _remoteFiles; @property(nonatomic, retain) DBRestClient *_restClient; @@ -46,7 +48,7 @@ -(void)getKDBFiles:(NSMutableArray *)list; -(id) readFile:(NSString *) fileName withPassword:(NSString *)password; --(id) readRemoteFile:(NSString *)filename withPassword:(NSString *)password useCached:(BOOL)useCached username:(NSString *)username userpass:(NSString *)userpass domain:(NSString *)domain; +-(id) readRemoteFile:(NSString *)filename withPassword:(NSString *)password useCached:(BOOL)useCached checkUpdate:(BOOL)checkUpdate username:(NSString *)username userpass:(NSString *)userpass domain:(NSString *)domain; -(void)deleteLocalFile:(NSString *)filename; @@ -71,5 +73,6 @@ -(BOOL)bIsDropBoxFileName:(NSString *)filename; -(BOOL)bIsDropBoxURL:(NSString *)url; - +-(BOOL)bCacheFileExists:(NSString *) filename; +-(BOOL)bCacheFileExitsForURL:(NSString *) url; @end diff --git a/Classes/UI/FileManager.m b/Classes/UI/FileManager.m index 840d0f3..b95c1ee 100644 --- a/Classes/UI/FileManager.m +++ b/Classes/UI/FileManager.m @@ -19,6 +19,8 @@ @interface FileManager(PrivateMethods) -(id) readFileHelp:(NSString *) fileName withPassword:(NSString *)password; +-(id) downLoadFile:(NSString *)url cacheFileName:(NSString *)cacheFileName tmpFileName:(NSString *) tmpFileName withPassword:(NSString *)password username:(NSString *)username userpass:(NSString *)userpass domain:(NSString *)domain; +-(id) checkForUpdate:(NSString *)url cacheFileName:(NSString *)cacheFileName tmpFileName:(NSString *) tmpFileName withPassword:(NSString *)password username:(NSString *)username userpass:(NSString *)userpass domain:(NSString *)domain; @end @@ -31,6 +33,7 @@ @implementation FileManager @synthesize _remoteFiles; @synthesize _cacheFileName; @synthesize _restClient; +@synthesize _tmpFileName; @synthesize _passwordViewController; @@ -86,6 +89,8 @@ -(void)dealloc{ [_password release]; [_filename release]; [_kdbReader release]; + [_cacheFileName release]; + [_tmpFileName release]; [super dealloc]; } @@ -123,7 +128,7 @@ +(NSString *)getTempFileNameFromURL:(NSString *)url{ return [DOWNLOAD_DIR stringByAppendingPathComponent:filename]; } --(id) readRemoteFile:(NSString *)filename withPassword:(NSString *)password useCached:(BOOL)useCached username:(NSString *)username userpass:(NSString *)userpass domain:(NSString *)domain{ +-(id) readRemoteFile:(NSString *)filename withPassword:(NSString *)password useCached:(BOOL)useCached checkUpdate:(BOOL)checkUpdate username:(NSString *)username userpass:(NSString *)userpass domain:(NSString *)domain{ id tree = nil; self._filename = filename; @@ -140,17 +145,50 @@ +(NSString *)getTempFileNameFromURL:(NSString *)url{ NSFileManager * fileManager = [NSFileManager defaultManager]; if([fileManager fileExistsAtPath:cacheFileName]&&useCached){ - id tree = [self readFileHelp:cacheFileName withPassword:password]; - _editable = NO; - if ([self bIsDropBoxURL:url]) { - [self._passwordViewController performSelector:@selector(fileOperationSuccess) withObject:nil]; + if (checkUpdate) { + tree = [self checkForUpdate:url cacheFileName:cacheFileName tmpFileName:tmp withPassword:password username:username userpass:userpass domain:domain]; } - return tree; + else { + id tree = [self readFileHelp:cacheFileName withPassword:password]; + _editable = NO; + if ([self bIsDropBoxURL:url]) { + [self._passwordViewController performSelector:@selector(fileOperationSuccess) withObject:nil]; + } + return tree; + } + return tree; } + tree = [self downLoadFile:url cacheFileName:cacheFileName tmpFileName:tmp withPassword:password username:username userpass:userpass domain:domain]; + + return tree; +} + +-(id) checkForUpdate:(NSString *)url cacheFileName:(NSString *)cacheFileName tmpFileName:(NSString *) tmpFileName withPassword:(NSString *)password username:(NSString *)username userpass:(NSString *)userpass domain:(NSString *)domain { + id tree = nil; + + if (![self bIsDropBoxURL:url]) { + tree = [self downLoadFile:url cacheFileName:cacheFileName tmpFileName:tmpFileName withPassword:password username:username userpass:userpass domain:domain]; + return tree; + } + else { + if (![[DBSession sharedSession] isLinked]) { + @throw [NSException exceptionWithName:@"RemoteAuthenticationError" reason:@"RemoteAuthenticationError" userInfo:nil]; + } + self._password = password; + self._tmpFileName = tmpFileName; + NSString *path = [url substringFromIndex:9]; + [[self _restClient] loadMetadata:path]; + } + return tree; +} + +-(id) downLoadFile:(NSString *)url cacheFileName:(NSString *)cacheFileName tmpFileName:(NSString *) tmpFileName withPassword:(NSString *)password username:(NSString *)username userpass:(NSString *)userpass domain:(NSString *)domain { + id tree = nil; + if (![self bIsDropBoxURL:url]) { ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:url]]; - [request setDownloadDestinationPath:tmp]; + [request setDownloadDestinationPath:tmpFileName]; if([username length]) [request setUsername:username]; @@ -172,7 +210,9 @@ +(NSString *)getTempFileNameFromURL:(NSString *)url{ } [[NSFileManager defaultManager] removeItemAtPath:cacheFileName error:nil]; - [[NSFileManager defaultManager] moveItemAtPath:tmp toPath:cacheFileName error:nil]; + [[NSFileManager defaultManager] moveItemAtPath:tmpFileName toPath:cacheFileName error:nil]; + _passwordViewController._switch.enabled = YES; + _passwordViewController._switch.on = YES; id tree = [self readFileHelp:cacheFileName withPassword:password]; return tree; } @@ -182,7 +222,7 @@ +(NSString *)getTempFileNameFromURL:(NSString *)url{ } self._password = password; NSString *path = [url substringFromIndex:9]; - [[self _restClient] loadFile:path intoPath:tmp]; + [[self _restClient] loadFile:path intoPath:tmpFileName]; } return tree; } @@ -273,6 +313,22 @@ -(BOOL)bIsDropBoxURL:(NSString *) url { return NO; } +-(BOOL)bCacheFileExists:(NSString *) filename { + NSString *url = [self getURLForRemoteFile:filename]; + return [self bCacheFileExitsForURL:url]; +} + +-(BOOL)bCacheFileExitsForURL:(NSString *) url { + NSString * cacheFileName = [FileManager getTempFileNameFromURL:url]; + + NSFileManager * fileManager = [NSFileManager defaultManager]; + + if([fileManager fileExistsAtPath:cacheFileName]) { + return YES; + } + return NO; +} + #pragma mark DBRestClient - (DBRestClient*)_restClient { @@ -286,23 +342,38 @@ - (DBRestClient*)_restClient { #pragma mark DBRestClientDelegate methods -/* - - (void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata { - NSLog(@"loadedMeta"); - } - - - (void)restClient:(DBRestClient*)client metadataUnchangedAtPath:(NSString*)path { - - NSLog(@"Metadata unchanged!"); - } +-(void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata { + NSFileManager * fileManager = [NSFileManager defaultManager]; + NSDictionary *fileAttributes; + NSError *errorFile = nil; + + fileAttributes = [fileManager attributesOfItemAtPath:_cacheFileName error:&errorFile]; + if (errorFile == nil) { + NSDate * localFileDate = [fileAttributes objectForKey:NSFileModificationDate]; + if ([[[metadata lastModifiedDate] laterDate:localFileDate] isEqual:localFileDate]) { + @try{ + id tree = [self readFileHelp:self._cacheFileName withPassword:self._password]; + tree = nil; + [self._passwordViewController performSelector:@selector(fileOperationSuccess) withObject:nil]; + }@catch(NSException * exception){ + [self._passwordViewController performSelector:@selector(fileOperationFailureWithException:) withObject:exception]; + } + return; + } + } + [[self _restClient] loadFile:metadata.path intoPath:_tmpFileName]; +} - - (void)restClient:(DBRestClient*)client loadMetadataFailedWithError:(NSError*)error { +-(void)restClient:(DBRestClient*)client metadataUnchangedAtPath:(NSString*)path { + // NSLog(@"Metadata unchanged!"); +} - NSLog(@"Error loading metadata: %@", error); - } - */ +-(void)restClient:(DBRestClient*)client loadMetadataFailedWithError:(NSError*)error { + NSException *exception = [NSException exceptionWithName:@"MetadataErrorWithUserInfo" reason:@"DownloadError" userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[error localizedDescription],@"error", nil]]; + [self._passwordViewController performSelector:@selector(fileOperationFailureWithException:) withObject:exception]; +} -- (void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath{ +-(void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath{ [[NSFileManager defaultManager] removeItemAtPath:self._cacheFileName error:nil]; [[NSFileManager defaultManager] moveItemAtPath:destPath toPath:self._cacheFileName error:nil]; @try{ @@ -313,11 +384,12 @@ - (void)restClient:(DBRestClient*)client loadedFile:(NSString*)destPath{ [self._passwordViewController performSelector:@selector(fileOperationFailureWithException:) withObject:exception]; } } -- (void)restClient:(DBRestClient*)client loadProgress:(CGFloat)progress forFile:(NSString*)destPath { + +-(void)restClient:(DBRestClient*)client loadProgress:(CGFloat)progress forFile:(NSString*)destPath { //NSLog(@"Progress"); } -- (void)restClient:(DBRestClient*)client loadFileFailedWithError:(NSError*)error { +-(void)restClient:(DBRestClient*)client loadFileFailedWithError:(NSError*)error { NSException *exception = [NSException exceptionWithName:@"DownloadErrorWithUserInfo" reason:@"DownloadError" userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[error localizedDescription],@"error", nil]]; [self._passwordViewController performSelector:@selector(fileOperationFailureWithException:) withObject:exception]; } diff --git a/Classes/UI/Main/FileManagerOperation.h b/Classes/UI/Main/FileManagerOperation.h index 933b2a6..bd9b265 100644 --- a/Classes/UI/Main/FileManagerOperation.h +++ b/Classes/UI/Main/FileManagerOperation.h @@ -27,6 +27,7 @@ NSString * _domain; BOOL _useCache; + BOOL _checkUpdate; } @property(nonatomic, retain) NSObject * _delegate; @@ -36,6 +37,8 @@ @property(nonatomic, retain) NSString * _userpass; @property(nonatomic, retain) NSString * _domain; @property(nonatomic, assign) BOOL _useCache; +@property(nonatomic, assign) BOOL _checkUpdate; + -(id)initWithDelegate:(id)delegate; diff --git a/Classes/UI/Main/FileManagerOperation.m b/Classes/UI/Main/FileManagerOperation.m index 6896437..a55924b 100644 --- a/Classes/UI/Main/FileManagerOperation.m +++ b/Classes/UI/Main/FileManagerOperation.m @@ -18,6 +18,7 @@ @implementation FileManagerOperation @synthesize _userpass; @synthesize _domain; @synthesize _useCache; +@synthesize _checkUpdate; @synthesize _delegate; -(id)initWithDelegate:(id)delegate{ @@ -55,7 +56,7 @@ -(void)openRemoteFile{ NSAutoreleasePool * pool = nil; @try{ pool = [[NSAutoreleasePool alloc] init]; - [[MyKeePassAppDelegate delegate]._fileManager readRemoteFile:_filename withPassword:_password useCached:_useCache username:_username userpass:_userpass domain:_domain]; + [[MyKeePassAppDelegate delegate]._fileManager readRemoteFile:_filename withPassword:_password useCached:_useCache checkUpdate:_checkUpdate username:_username userpass:_userpass domain:_domain]; [_delegate performSelectorOnMainThread:@selector(fileOperationSuccess) withObject:nil waitUntilDone:NO]; }@catch(NSException * exception){ [_delegate performSelectorOnMainThread:@selector(fileOperationFailureWithException:) withObject:exception waitUntilDone:NO]; @@ -69,7 +70,7 @@ -(void)openRemoteFile{ -(void)openDropboxRemoteFile{ @try{ [MyKeePassAppDelegate delegate]._fileManager._passwordViewController = self._delegate; - [[MyKeePassAppDelegate delegate]._fileManager readRemoteFile:_filename withPassword:_password useCached:_useCache username:_username userpass:_userpass domain:_domain]; + [[MyKeePassAppDelegate delegate]._fileManager readRemoteFile:_filename withPassword:_password useCached:_useCache checkUpdate:_checkUpdate username:_username userpass:_userpass domain:_domain]; //[_delegate performSelectorOnMainThread:@selector(fileOperationSuccess) withObject:nil waitUntilDone:NO]; }@catch(NSException * exception){ [_delegate performSelectorOnMainThread:@selector(fileOperationFailureWithException:) withObject:exception waitUntilDone:NO]; diff --git a/Classes/UI/Main/PasswordViewController.h b/Classes/UI/Main/PasswordViewController.h index e676a13..6e693c3 100644 --- a/Classes/UI/Main/PasswordViewController.h +++ b/Classes/UI/Main/PasswordViewController.h @@ -22,9 +22,11 @@ ActivityView * _av; UIButton * _ok; UIButton * _cancel; - UISwitch * _switch; + UISwitch * _switch; + UISwitch * _switchCheckUpdate; TextFieldCell * _password; UITableViewCell * _useCache; + UITableViewCell *_checkUpdate; TextFieldCell * _rusername; TextFieldCell * _rpassword; TextFieldCell * _rdomain; @@ -38,9 +40,11 @@ @property(nonatomic, retain) UIButton * _ok; @property(nonatomic, retain) UIButton * _cancel; @property(nonatomic, retain) UISwitch * _switch; +@property(nonatomic, retain) UISwitch * _switchCheckUpdate; @property(nonatomic, retain) TextFieldCell * _password; @property(nonatomic, retain) UITableViewCell * _useCache; +@property(nonatomic, retain) UITableViewCell * _checkUpdate; @property(nonatomic, retain) TextFieldCell * _rusername; @property(nonatomic, retain) TextFieldCell * _rpassword; @property(nonatomic, retain) TextFieldCell * _rdomain; diff --git a/Classes/UI/Main/PasswordViewController.m b/Classes/UI/Main/PasswordViewController.m index 891051f..f81027a 100644 --- a/Classes/UI/Main/PasswordViewController.m +++ b/Classes/UI/Main/PasswordViewController.m @@ -25,9 +25,11 @@ @implementation PasswordViewController @synthesize _ok; @synthesize _cancel; @synthesize _switch; +@synthesize _switchCheckUpdate; @synthesize _av; @synthesize _password; @synthesize _useCache; +@synthesize _checkUpdate; @synthesize _rusername; @synthesize _rpassword; @synthesize _rdomain; @@ -50,6 +52,7 @@ - (void)dealloc { [_av release]; [_password release]; [_switch release]; + [_switchCheckUpdate release]; [_useCache release]; [_rusername release]; [_rpassword release]; @@ -93,10 +96,28 @@ -(void)viewDidLoad{ _useCache.textLabel.textColor = [UIColor darkGrayColor]; _useCache.selectionStyle = UITableViewCellSelectionStyleNone; + _checkUpdate = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; + _checkUpdate.textLabel.text = NSLocalizedString(@"Check for update", @"Check for update"); + _checkUpdate.textLabel.font = [UIFont systemFontOfSize:17]; + _checkUpdate.textLabel.textColor = [UIColor darkGrayColor]; + _checkUpdate.selectionStyle = UITableViewCellSelectionStyleNone; + _switch = [[UISwitch alloc]initWithFrame:CGRectMake(195, 6, 94, 27)]; - _switch.on = YES; + [_switch addTarget:self action:@selector(useCacheChanged:) forControlEvents:UIControlEventValueChanged]; + + _switch.on = YES; + + _switchCheckUpdate = [[UISwitch alloc]initWithFrame:CGRectMake(195, 6, 94, 27)]; + _switchCheckUpdate.on = NO; + + if (![[MyKeePassAppDelegate delegate]._fileManager bCacheFileExists:_filename]) { + _switch.on = NO; + _switch.enabled = NO; + _switchCheckUpdate.enabled = NO; + } - [_useCache.contentView addSubview:_switch]; + [_useCache.contentView addSubview:_switch]; + [_checkUpdate.contentView addSubview:_switchCheckUpdate]; //set the footer; UIView * container = [[UIView alloc]initWithFrame:CGRectMake(0,0,320,44)]; @@ -155,7 +176,17 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Customize the number of rows in the table view. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if(section==0){ - return _isRemote?2:1; + if (!_isRemote) { + return 1; + } + else { + if ([[MyKeePassAppDelegate delegate]._fileManager bIsDropBoxFileName:_filename]){ + return 3; + } + else { + return 2; + } + } }else{ return 3; } @@ -164,10 +195,18 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger // Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if([indexPath section]==0){ - if([indexPath row]==0) - return _password; - else - return _useCache; + switch ([indexPath row]) { + case 0: + return _password; + break; + case 1: + return _useCache; + break; + case 2: + return _checkUpdate; + default: + break; + } }else{ switch ([indexPath row]) { case 0: @@ -198,11 +237,13 @@ -(IBAction)okClicked:(id)sender{ [self.view addSubview:_av]; if(!_op) _op = [[FileManagerOperation alloc]initWithDelegate:self]; _op._filename = _filename; - _op._password = _password._field.text; + _op._password = _password._field.text; + if(!self._isRemote){ [_op performSelectorInBackground:@selector(openLocalFile) withObject:nil]; }else{ _op._useCache = _switch.on; + _op._checkUpdate = _switchCheckUpdate.on; _op._username = _rusername._field.text; _op._userpass = _rpassword._field.text; _op._domain = _rdomain._field.text; @@ -224,6 +265,8 @@ -(IBAction)okClicked:(id)sender{ } -(void)fileOperationSuccess{ + _switch.enabled = YES; + _switch.on = YES; [_av removeFromSuperview]; [[NSNotificationCenter defaultCenter] postNotificationName:@"DismissModalViewOK" object:self]; } @@ -232,6 +275,8 @@ -(void)fileOperationFailureWithException:(NSException *)exception{ [_av removeFromSuperview]; _ok.enabled = _cancel.enabled = YES; _isLoading = NO; + _switch.enabled = YES; + _switch.on = YES; if([[exception name] isEqualToString:@"DecryptError"]){ [self showError:NSLocalizedString(@"Master password is not correct", @"Master password is not correct")]; @@ -266,5 +311,15 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField{ return NO; } +-(void)useCacheChanged:(UISwitch *)sender { + if (!sender.on) { + _switchCheckUpdate.on = NO; + _switchCheckUpdate.enabled = NO; + } + else { + _switchCheckUpdate.enabled = YES; + } +} + @end From 87ac4e8857a01f1069e4bf7b22c444098dd76173 Mon Sep 17 00:00:00 2001 From: jrroca Date: Tue, 10 May 2011 22:13:51 +0200 Subject: [PATCH 6/6] Added exception handling. --- Classes/UI/FileManager.m | 1 - Classes/UI/Main/PasswordViewController.m | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Classes/UI/FileManager.m b/Classes/UI/FileManager.m index b95c1ee..6122de1 100644 --- a/Classes/UI/FileManager.m +++ b/Classes/UI/FileManager.m @@ -353,7 +353,6 @@ -(void)restClient:(DBRestClient*)client loadedMetadata:(DBMetadata*)metadata { if ([[[metadata lastModifiedDate] laterDate:localFileDate] isEqual:localFileDate]) { @try{ id tree = [self readFileHelp:self._cacheFileName withPassword:self._password]; - tree = nil; [self._passwordViewController performSelector:@selector(fileOperationSuccess) withObject:nil]; }@catch(NSException * exception){ [self._passwordViewController performSelector:@selector(fileOperationFailureWithException:) withObject:exception]; diff --git a/Classes/UI/Main/PasswordViewController.m b/Classes/UI/Main/PasswordViewController.m index f81027a..5c797c9 100644 --- a/Classes/UI/Main/PasswordViewController.m +++ b/Classes/UI/Main/PasswordViewController.m @@ -286,6 +286,8 @@ -(void)fileOperationFailureWithException:(NSException *)exception{ [msg release]; }else if([[exception name] isEqualToString:@"DownloadError"]){ [self showError:NSLocalizedString(@"Cannot download the file", @"Cannot download the file")]; + }else if([[exception name] isEqualToString:@"MetadataErrorWithUserInfo"]){ + [self showError:NSLocalizedString(@"Cannot download the file", @"Cannot download the file")]; }else if([[exception name] isEqualToString:@"DownloadErrorWithUserInfo"]){ [self showError:[NSString stringWithFormat:@"%@.%@", NSLocalizedString(@"Cannot download the file", @"Cannot download the file"),[[exception userInfo] valueForKey:@"error"]]]; }else if ([[exception name] isEqualToString:@"RemoteAuthenticationError"]){