├── .gitignore ├── .gitmodules ├── Default-568h@2x.png ├── README.md ├── RSOAuthEngine ├── RSOAuthEngine.h └── RSOAuthEngine.m ├── TwitterDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── TwitterDemo ├── AppDelegate.h ├── AppDelegate.m ├── Twitter │ ├── RSTwitterEngine.h │ └── RSTwitterEngine.m ├── TwitterDemo-Info.plist ├── TwitterDemo-Prefix.pch ├── ViewController.h ├── ViewController.m ├── WebViewController.h ├── WebViewController.m ├── en.lproj │ ├── InfoPlist.strings │ ├── ViewController.xib │ └── WebViewController.xib └── main.m ├── logo_cube_114.png ├── logo_cube_57.png └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.xcodeproj/* 2 | !*.xcodeproj/project.pbxproj 3 | !*.xcodeproj/project.xcworkspace 4 | *.xcworkspace/* 5 | !*.xcworkspace/contents.xcworkspacedata 6 | /build 7 | *.pbxuser 8 | *.mode1v3 9 | *.mode2v3 10 | *.perspectivev3 11 | target/* 12 | .DS_Store 13 | profile 14 | *.svn 15 | /.svn/* 16 | xcuserdata 17 | Thumbs.db 18 | *.xcworkspacedata 19 | *.xccheckout -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "MKNetworkKit"] 2 | path = MKNetworkKit 3 | url = git://github.com/MugunthKumar/MKNetworkKit.git 4 | -------------------------------------------------------------------------------- /Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsieiro/RSOAuthEngine/ba43ddb52601df819b37e0e1a7257ca9d9ff815a/Default-568h@2x.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RSOAuthEngine 2 | By Rodrigo Sieiro - [@cheapo](http://twitter.com/cheapo) 3 | [http://rodrigo.sharpcube.com](http://rodrigo.sharpcube.com) 4 | 5 | ## About 6 | 7 | **RSOAuthEngine** is an ARC based OAuth engine for [MKNetworkKit](https://github.com/MugunthKumar/MKNetworkKit). It supports OAuth 1.0a and it's fully compatible with MKNetworkKit existing classes, allowing you to simply inherit `RSOAuthEngine` instead of `MKNetworkEngine` to get OAuth support. 8 | 9 | ## Usage 10 | 11 | If you already have a project using MKNetworkKit, just add the contents of the `RSOAuthEngine` directory to your project and change all classes that inherit from `MKNetworkEngine` to inherit from `RSOAuthEngine` instead. Whenever you need to send an OAuth signed request, replace calls to `enqueueOperation` with `enqueueSignedOperation`. 12 | 13 | If you're not currently using MKNetworkKit, follow the instructions to add it to your project [here](http://blog.mugunthkumar.com/products/ios-framework-introducing-mknetworkkit/) first, then add **RSOAuthEngine** as written in the previous paragraph. **Important**: although not mentioned in the instructions, MKNetworkKit also requires Security.framework. 14 | 15 | ### Usage Details 16 | 17 | A common OAuth flow using **RSOAuthEngine** should go like this: 18 | 19 | 1. Create a class that inherits from **RSOAuthEngine**. 20 | 2. Init your class using one of the defined initializers that include your Consumer Key and Secret. 21 | 3. Send a signed operation to get a request token. 22 | 4. Fill the request token using `fillTokenWithResponseBody:type` (use `RSOAuthRequestToken` as type). 23 | 5. Redirect the user to the authorization page and wait for the callback. 24 | 6. Fill the request token (again) using `fillTokenWithResponseBody:type` (use `RSOAuthRequestToken` as type), this time using the parameters received in the callback. 25 | 7. Send another request to get an access token. 26 | 8. Fill the access token using `fillTokenWithResponseBody:type` (use `RSOAuthAccessToken` as type). 27 | 9. From now on, all requests sent with `enqueueSignedOperation` will be signed with your tokens. 28 | 29 | Alternatively you could use `setAccessToken:secret` after initialization to define a previously stored access token. If you need or want to use xAuth instead of the request token/authorize workflow, please take a look at the Instapaper demo. 30 | 31 | ### XOAuth 32 | 33 | This library also supports generating XOAuth strings (to use with Gmail SMTP and IMAP servers, for example). Just call `generateXOAuthStringForURL:method` with the desired URL and method (GET/POST). 34 | 35 | ## Twitter Demo 36 | 37 | 38 | 39 | 42 | 60 | 61 |
40 | Screenshot 41 | 43 |

About

44 | 45 |

This sample project demonstrates how to use RSOAuthEngine to authenticate with Twitter. It includes a basic engine that implements Twitter's OAuth authentication flow and allows you to post a tweet. It also shows you how to persist the OAuth access token in the Keychain. The Twitter engine should not be considered production code, and is only included to demonstrate RSOAuthEngine.

46 | 47 |

Building

48 | 49 |

To build the demo project, follow these steps:

50 | 51 |
    52 |
  1. In the project directory, run git submodule update --init to retrieve MKNetworkKit (added to the project as a submodule).
  2. 53 |
  3. Put your consumer key and secret at the top of RSTwitterEngine.m and remove the #error macro. If you don't have a consumer key/secret, register an app at https://dev.twitter.com/apps to get a pair. Important: you need to add a dummy callback URL to your app when registering, otherwise Twitter won't allow you to send a callback URL in the OAuth request.
  4. 54 |
55 | 56 |

Tips

57 | 58 |

Swipe from left to right in the status message to clear previously stored OAuth tokens.

59 |
62 | 63 | ## Compatibility 64 | 65 | Currently this engine has been tested with Twitter and Instapaper. If you use **RSOAuthEngine** to implement OAuth authentication with another service, please let me know so I can update this section. 66 | 67 | ## License 68 | 69 | **RSOAuthEngine** is licensed under the MIT License. Please give me some kind of attribution if you use it in your project, such as a "thanks" note somewhere. I'd also love to know if you use my code, please drop me a line if you do! 70 | 71 | Full license text follows: 72 | 73 | Permission is hereby granted, free of charge, to any person obtaining a copy 74 | of this software and associated documentation files (the "Software"), to deal 75 | in the Software without restriction, including without limitation the rights 76 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 77 | copies of the Software, and to permit persons to whom the Software is 78 | furnished to do so, subject to the following conditions: 79 | 80 | The above copyright notice and this permission notice shall be included in 81 | all copies or substantial portions of the Software. 82 | 83 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 84 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 85 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 86 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 87 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 88 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 89 | THE SOFTWARE. 90 | 91 | ## Acknowledgments 92 | 93 | **RSOAuthEngine** may contain code from [ASI-HTTP-Request-OAuth](https://github.com/keybuk/asi-http-request-oauth) by Scott James Remnant and the iPhone version of [OAuthConsumer](https://github.com/jdg/oauthconsumer) by Jonathan George. I used bits and pieces of the code from both projects as references to write this engine. -------------------------------------------------------------------------------- /RSOAuthEngine/RSOAuthEngine.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSOAuthEngine.h 3 | // RSOAuthEngine 4 | // 5 | // Created by Rodrigo Sieiro on 12/11/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "MKNetworkEngine.h" 27 | 28 | typedef enum _RSOAuthTokenType 29 | { 30 | RSOAuthRequestToken, 31 | RSOAuthRequestAccessToken, 32 | RSOAuthAccessToken, 33 | } 34 | RSOAuthTokenType; 35 | 36 | typedef enum _RSOOAuthSignatureMethod { 37 | RSOAuthPlainText, 38 | RSOAuthHMAC_SHA1, 39 | } RSOAuthSignatureMethod; 40 | 41 | typedef enum _RSOAuthParameterStyle { 42 | RSOAuthParameterStyleHeader, 43 | RSOAuthParameterStylePostBody, 44 | RSOAuthParameterStyleQueryString 45 | } RSOAuthParameterStyle; 46 | 47 | @interface RSOAuthEngine : MKNetworkEngine 48 | { 49 | @private 50 | RSOAuthTokenType _tokenType; 51 | RSOAuthSignatureMethod _signatureMethod; 52 | NSString *_consumerSecret; 53 | NSString *_tokenSecret; 54 | NSString *_callbackURL; 55 | NSString *_verifier; 56 | NSMutableDictionary *_oAuthValues; 57 | NSMutableDictionary *_customValues; 58 | } 59 | 60 | @property (readonly) RSOAuthTokenType tokenType; 61 | @property (readonly) RSOAuthSignatureMethod signatureMethod; 62 | @property (readonly) NSString *consumerKey; 63 | @property (readonly) NSString *consumerSecret; 64 | @property (readonly) NSString *callbackURL; 65 | @property (readonly) NSString *token; 66 | @property (readonly) NSString *tokenSecret; 67 | @property (readonly) NSString *verifier; 68 | 69 | @property (nonatomic, assign) RSOAuthParameterStyle parameterStyle; 70 | 71 | - (id)initWithHostName:(NSString *)hostName 72 | customHeaderFields:(NSDictionary *)headers 73 | signatureMethod:(RSOAuthSignatureMethod)signatureMethod 74 | consumerKey:(NSString *)consumerKey 75 | consumerSecret:(NSString *)consumerSecret 76 | callbackURL:(NSString *)callbackURL; 77 | 78 | - (id)initWithHostName:(NSString *)hostName 79 | customHeaderFields:(NSDictionary *)headers 80 | signatureMethod:(RSOAuthSignatureMethod)signatureMethod 81 | consumerKey:(NSString *)consumerKey 82 | consumerSecret:(NSString *)consumerSecret; 83 | 84 | - (BOOL)isAuthenticated; 85 | - (void)resetOAuthToken; 86 | - (NSString *)customValueForKey:(NSString *)key; 87 | - (void)fillTokenWithResponseBody:(NSString *)body type:(RSOAuthTokenType)tokenType; 88 | - (void)setAccessToken:(NSString *)token secret:(NSString *)tokenSecret; 89 | - (void)signRequest:(MKNetworkOperation *)request; 90 | - (void)enqueueSignedOperation:(MKNetworkOperation *)op; 91 | - (NSString *)generateXOAuthStringForURL:(NSString *)url method:(NSString *)method; 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /RSOAuthEngine/RSOAuthEngine.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSOAuthEngine.m 3 | // RSOAuthEngine 4 | // 5 | // Created by Rodrigo Sieiro on 12/11/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #include 27 | #import 28 | #import "NSData+MKBase64.h" 29 | #import "NSString+MKNetworkKitAdditions.h" 30 | #import "RSOAuthEngine.h" 31 | 32 | static const NSString *oauthVersion = @"1.0"; 33 | 34 | static const NSString *oauthSignatureMethodName[] = { 35 | @"PLAINTEXT", 36 | @"HMAC-SHA1", 37 | }; 38 | 39 | // This category for MKNetworkOperation was added 40 | // Because we need access to these fields 41 | // And they are private inside the class 42 | 43 | @interface MKNetworkOperation (RSO) 44 | 45 | @property (strong, nonatomic) NSMutableURLRequest *request; 46 | @property (strong, nonatomic) NSMutableDictionary *fieldsToBePosted; 47 | @property (strong, nonatomic) NSMutableArray *filesToBePosted; 48 | @property (strong, nonatomic) NSMutableArray *dataToBePosted; 49 | 50 | - (void)rs_setURL:(NSURL *)URL; 51 | - (void)rs_setValue:(NSString *)value forKey:(NSString *)key; 52 | 53 | @end 54 | 55 | @implementation MKNetworkOperation (RSO) 56 | 57 | @dynamic request; 58 | @dynamic fieldsToBePosted; 59 | @dynamic filesToBePosted; 60 | @dynamic dataToBePosted; 61 | 62 | - (void)rs_setURL:(NSURL *)URL 63 | { 64 | [self.request setURL:URL]; 65 | } 66 | 67 | - (void)rs_setValue:(NSString *)value forKey:(NSString *)key 68 | { 69 | [self.fieldsToBePosted setObject:value forKey:key]; 70 | } 71 | 72 | @end 73 | 74 | @interface RSOAuthEngine () 75 | 76 | - (NSString *)signatureBaseStringForURL:(NSString *)url method:(NSString *)method parameters:(NSMutableArray *)parameters; 77 | - (NSString *)signatureBaseStringForRequest:(MKNetworkOperation *)request; 78 | - (NSString *)generatePlaintextSignatureFor:(NSString *)baseString; 79 | - (NSString *)generateHMAC_SHA1SignatureFor:(NSString *)baseString; 80 | - (void)addCustomValue:(NSString *)value withKey:(NSString *)key; 81 | - (void)setOAuthValue:(NSString *)value forKey:(NSString *)key; 82 | 83 | @end 84 | 85 | @implementation RSOAuthEngine 86 | 87 | #pragma mark - Read-only Properties 88 | 89 | - (NSString *)consumerKey 90 | { 91 | return (_oAuthValues) ? [_oAuthValues objectForKey:@"oauth_consumer_key"] : @""; 92 | } 93 | 94 | - (NSString *)token 95 | { 96 | return (_oAuthValues) ? [_oAuthValues objectForKey:@"oauth_token"] : @""; 97 | } 98 | 99 | #pragma mark - Initialization 100 | 101 | - (id)initWithHostName:(NSString *)hostName 102 | customHeaderFields:(NSDictionary *)headers 103 | signatureMethod:(RSOAuthSignatureMethod)signatureMethod 104 | consumerKey:(NSString *)consumerKey 105 | consumerSecret:(NSString *)consumerSecret 106 | callbackURL:(NSString *)callbackURL 107 | { 108 | NSAssert(consumerKey, @"Consumer Key cannot be null"); 109 | NSAssert(consumerSecret, @"Consumer Secret cannot be null"); 110 | 111 | self = [super initWithHostName:hostName customHeaderFields:headers]; 112 | 113 | if (self) { 114 | _consumerSecret = consumerSecret; 115 | _callbackURL = callbackURL; 116 | _signatureMethod = signatureMethod; 117 | 118 | _oAuthValues = [@{ 119 | @"oauth_version": oauthVersion, 120 | @"oauth_signature_method": oauthSignatureMethodName[_signatureMethod], 121 | @"oauth_consumer_key": consumerKey, 122 | @"oauth_token": @"", 123 | @"oauth_verifier": @"", 124 | @"oauth_callback": @"", 125 | @"oauth_signature": @"", 126 | @"oauth_timestamp": @"", 127 | @"oauth_nonce": @"", 128 | @"realm": @"" 129 | } mutableCopy]; 130 | 131 | [self resetOAuthToken]; 132 | 133 | // By default, add the OAuth parameters to the Authorization header 134 | self.parameterStyle = RSOAuthParameterStyleHeader; 135 | } 136 | 137 | return self; 138 | } 139 | 140 | - (id)initWithHostName:(NSString *)hostName 141 | customHeaderFields:(NSDictionary *)headers 142 | signatureMethod:(RSOAuthSignatureMethod)signatureMethod 143 | consumerKey:(NSString *)consumerKey 144 | consumerSecret:(NSString *)consumerSecret 145 | { 146 | return [self initWithHostName:hostName 147 | customHeaderFields:headers 148 | signatureMethod:signatureMethod 149 | consumerKey:consumerKey 150 | consumerSecret:consumerSecret 151 | callbackURL:nil]; 152 | } 153 | 154 | #pragma mark - OAuth Signature Generators 155 | 156 | - (NSString *)signatureBaseStringForURL:(NSString *)url method:(NSString *)method parameters:(NSMutableArray *)parameters 157 | { 158 | // Create a NSMutableArray if not created yet 159 | if (!parameters) { 160 | parameters = [NSMutableArray arrayWithCapacity:[_oAuthValues count]]; 161 | } 162 | 163 | // Add parameters from the OAuth header 164 | [_oAuthValues enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) { 165 | if ([key hasPrefix:@"oauth_"] && ![key isEqualToString:@"oauth_signature"] && obj && ![obj isEqualToString:@""]) { 166 | [parameters addObject:@{ 167 | @"key": [key mk_urlEncodedString], 168 | @"value": [obj mk_urlEncodedString] 169 | }]; 170 | } 171 | }]; 172 | 173 | // Sort by name and value 174 | [parameters sortUsingComparator:^(id obj1, id obj2) { 175 | NSDictionary *val1 = obj1, *val2 = obj2; 176 | NSComparisonResult result = [[val1 objectForKey:@"key"] compare:[val2 objectForKey:@"key"] options:NSLiteralSearch]; 177 | if (result != NSOrderedSame) return result; 178 | return [[val1 objectForKey:@"value"] compare:[val2 objectForKey:@"value"] options:NSLiteralSearch]; 179 | }]; 180 | 181 | // Join sorted components 182 | NSMutableArray *normalizedParameters = [NSMutableArray array]; 183 | [parameters enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { 184 | [normalizedParameters addObject:[NSString stringWithFormat:@"%@=%@", [obj objectForKey:@"key"], [obj objectForKey:@"value"]]]; 185 | }]; 186 | 187 | // Create the signature base string 188 | NSString *signatureBaseString = [NSString stringWithFormat:@"%@&%@&%@", 189 | [method uppercaseString], 190 | [url mk_urlEncodedString], 191 | [[normalizedParameters componentsJoinedByString:@"&"] mk_urlEncodedString]]; 192 | 193 | return signatureBaseString; 194 | } 195 | 196 | - (NSString *)signatureBaseStringForRequest:(MKNetworkOperation *)request 197 | { 198 | NSMutableArray *parameters = [NSMutableArray array]; 199 | NSURL *url = [NSURL URLWithString:request.url]; 200 | 201 | // Get the base URL String (with no parameters) 202 | NSArray *urlParts = [request.url componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"?#"]]; 203 | NSString *baseURL = [urlParts objectAtIndex:0]; 204 | 205 | // Only include GET and POST fields if there are no files or data to be posted 206 | if ([request.filesToBePosted count] == 0 && [request.dataToBePosted count] == 0) { 207 | // Add parameters from the query string 208 | NSArray *pairs = [url.query componentsSeparatedByString:@"&"]; 209 | [pairs enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) { 210 | NSArray *elements = [obj componentsSeparatedByString:@"="]; 211 | NSString *key = [elements objectAtIndex:0]; 212 | NSString *value = (elements.count > 1) ? [elements objectAtIndex:1] : @""; 213 | 214 | [parameters addObject:@{@"key": key, @"value": value}]; 215 | }]; 216 | 217 | // Add parameters from the request body 218 | // Only if we're POSTing, GET parameters were already added 219 | if ([[[request HTTPMethod] uppercaseString] isEqualToString:@"POST"] && [request postDataEncoding] == MKNKPostDataEncodingTypeURL) { 220 | [request.readonlyPostDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 221 | if([obj isKindOfClass:[NSString class]]) { 222 | [parameters addObject:@{ 223 | @"key": [key mk_urlEncodedString], 224 | @"value": [obj mk_urlEncodedString] 225 | }]; 226 | } else { 227 | [parameters addObject:@{ 228 | @"key": [key mk_urlEncodedString], 229 | @"value": [NSString stringWithFormat:@"%@", obj] 230 | }]; 231 | } 232 | }]; 233 | } 234 | } 235 | 236 | return [self signatureBaseStringForURL:baseURL method:[request HTTPMethod] parameters:parameters]; 237 | } 238 | 239 | - (NSString *)generatePlaintextSignatureFor:(NSString *)baseString 240 | { 241 | return [NSString stringWithFormat:@"%@&%@", 242 | self.consumerSecret != nil ? [self.consumerSecret mk_urlEncodedString] : @"", 243 | self.tokenSecret != nil ? [self.tokenSecret mk_urlEncodedString] : @""]; 244 | } 245 | 246 | - (NSString *)generateHMAC_SHA1SignatureFor:(NSString *)baseString 247 | { 248 | NSString *key = [self generatePlaintextSignatureFor:baseString]; 249 | 250 | const char *keyBytes = [key cStringUsingEncoding:NSUTF8StringEncoding]; 251 | const char *baseStringBytes = [baseString cStringUsingEncoding:NSUTF8StringEncoding]; 252 | unsigned char digestBytes[CC_SHA1_DIGEST_LENGTH]; 253 | 254 | CCHmacContext ctx; 255 | CCHmacInit(&ctx, kCCHmacAlgSHA1, keyBytes, strlen(keyBytes)); 256 | CCHmacUpdate(&ctx, baseStringBytes, strlen(baseStringBytes)); 257 | CCHmacFinal(&ctx, digestBytes); 258 | 259 | NSData *digestData = [NSData dataWithBytes:digestBytes length:CC_SHA1_DIGEST_LENGTH]; 260 | return [digestData base64EncodedString]; 261 | } 262 | 263 | #pragma mark - Dictionary Helpers 264 | 265 | - (void)addCustomValue:(NSString *)value withKey:(NSString *)key 266 | { 267 | if (!_customValues) _customValues = [[NSMutableDictionary alloc] initWithCapacity:1]; 268 | 269 | if (value) { 270 | [_customValues setObject:value forKey:key]; 271 | } else { 272 | [_customValues setObject:@"" forKey:key]; 273 | } 274 | } 275 | 276 | - (void)setOAuthValue:(NSString *)value forKey:(NSString *)key 277 | { 278 | if (value) { 279 | [_oAuthValues setObject:value forKey:key]; 280 | } else { 281 | [_oAuthValues setObject:@"" forKey:key]; 282 | } 283 | } 284 | 285 | #pragma mark - Public Methods 286 | 287 | - (BOOL)isAuthenticated 288 | { 289 | return (_tokenType == RSOAuthAccessToken && self.token && self.tokenSecret); 290 | } 291 | 292 | - (void)resetOAuthToken 293 | { 294 | _tokenType = RSOAuthRequestToken; 295 | _tokenSecret = nil; 296 | _verifier = nil; 297 | _customValues = nil; 298 | 299 | [self setOAuthValue:self.callbackURL forKey:@"oauth_callback"]; 300 | [self setOAuthValue:@"" forKey:@"oauth_verifier"]; 301 | [self setOAuthValue:@"" forKey:@"oauth_token"]; 302 | } 303 | 304 | - (NSString *)customValueForKey:(NSString *)key 305 | { 306 | if (!_customValues) return nil; 307 | return [_customValues objectForKey:key]; 308 | } 309 | 310 | - (void)fillTokenWithResponseBody:(NSString *)body type:(RSOAuthTokenType)tokenType 311 | { 312 | NSArray *pairs = [body componentsSeparatedByString:@"&"]; 313 | 314 | [pairs enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) { 315 | NSArray *elements = [obj componentsSeparatedByString:@"="]; 316 | NSString *key = [[elements objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 317 | NSString *value = [[elements objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 318 | 319 | if ([key isEqualToString:@"oauth_token"]) { 320 | [self setOAuthValue:value forKey:@"oauth_token"]; 321 | } else if ([key isEqualToString:@"oauth_token_secret"]) { 322 | _tokenSecret = value; 323 | } else if ([key isEqualToString:@"oauth_verifier"]) { 324 | _verifier = value; 325 | } else { 326 | [self addCustomValue:value withKey:key]; 327 | } 328 | }]; 329 | 330 | _tokenType = tokenType; 331 | 332 | // If we already have an Access Token, no need to send the Verifier and Callback URL 333 | if (_tokenType == RSOAuthAccessToken) { 334 | [self setOAuthValue:nil forKey:@"oauth_callback"]; 335 | [self setOAuthValue:nil forKey:@"oauth_verifier"]; 336 | } else if (_tokenType == RSOAuthRequestAccessToken) { 337 | [self setOAuthValue:nil forKey:@"oauth_callback"]; 338 | [self setOAuthValue:self.verifier forKey:@"oauth_verifier"]; 339 | } else { 340 | [self setOAuthValue:self.callbackURL forKey:@"oauth_callback"]; 341 | [self setOAuthValue:self.verifier forKey:@"oauth_verifier"]; 342 | } 343 | } 344 | 345 | - (void)setAccessToken:(NSString *)token secret:(NSString *)tokenSecret 346 | { 347 | NSAssert(token, @"Token cannot be null"); 348 | NSAssert(tokenSecret, @"Token Secret cannot be null"); 349 | 350 | [self resetOAuthToken]; 351 | 352 | [self setOAuthValue:token forKey:@"oauth_token"]; 353 | _tokenSecret = tokenSecret; 354 | _tokenType = RSOAuthAccessToken; 355 | 356 | // Since we already have an Access Token, no need to send the Verifier and Callback URL 357 | [self setOAuthValue:nil forKey:@"oauth_callback"]; 358 | [self setOAuthValue:nil forKey:@"oauth_verifier"]; 359 | } 360 | 361 | - (void)signRequest:(MKNetworkOperation *)request 362 | { 363 | NSAssert(_oAuthValues && self.consumerKey && self.consumerSecret, @"Please use an initializer with Consumer Key and Consumer Secret."); 364 | 365 | // Generate timestamp and nonce values 366 | [self setOAuthValue:[NSString stringWithFormat:@"%ld", time(NULL)] forKey:@"oauth_timestamp"]; 367 | [self setOAuthValue:[NSString uniqueString] forKey:@"oauth_nonce"]; 368 | 369 | // Construct the signature base string 370 | NSString *baseString = [self signatureBaseStringForRequest:request]; 371 | 372 | // Generate the signature 373 | switch (_signatureMethod) { 374 | case RSOAuthHMAC_SHA1: 375 | [self setOAuthValue:[self generateHMAC_SHA1SignatureFor:baseString] forKey:@"oauth_signature"]; 376 | break; 377 | default: 378 | [self setOAuthValue:[self generatePlaintextSignatureFor:baseString] forKey:@"oauth_signature"]; 379 | break; 380 | } 381 | 382 | if (self.parameterStyle == RSOAuthParameterStyleHeader) { 383 | NSMutableArray *oauthHeaders = [NSMutableArray array]; 384 | 385 | [_oAuthValues enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) { 386 | if (obj && ![obj isEqualToString:@""]) { 387 | [oauthHeaders addObject:[NSString stringWithFormat:@"%@=\"%@\"", [key mk_urlEncodedString], [obj mk_urlEncodedString]]]; 388 | } 389 | }]; 390 | 391 | // Set the Authorization header 392 | NSString *oauthData = [NSString stringWithFormat:@"OAuth %@", [oauthHeaders componentsJoinedByString:@", "]]; 393 | NSDictionary *oauthHeader = @{@"Authorization": oauthData}; 394 | 395 | [request addHeaders:oauthHeader]; 396 | } else if (self.parameterStyle == RSOAuthParameterStylePostBody && [request.readonlyRequest.HTTPMethod caseInsensitiveCompare:@"GET"] != NSOrderedSame) { 397 | [_oAuthValues enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) { 398 | if (obj && ![obj isEqualToString:@""]) { 399 | [request rs_setValue:obj forKey:key]; 400 | } 401 | }]; 402 | } else { // self.parameterStyle == RSOAuthParameterStyleQueryString 403 | NSMutableArray *oauthParams = [NSMutableArray array]; 404 | 405 | // Fill the authorization header array 406 | [_oAuthValues enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) { 407 | if (obj && ![obj isEqualToString:@""]) { 408 | [oauthParams addObject:[NSString stringWithFormat:@"%@=%@", [key mk_urlEncodedString], [obj mk_urlEncodedString]]]; 409 | } 410 | }]; 411 | 412 | NSString *url = request.url; 413 | NSString *separator = [url rangeOfString:@"?"].length > 0 ? @"&" : @"?"; 414 | url = [NSString stringWithFormat:@"%@%@%@", url, separator, [oauthParams componentsJoinedByString:@"&"]]; 415 | [request rs_setURL:[NSURL URLWithString:url]]; 416 | } 417 | } 418 | 419 | - (void)enqueueSignedOperation:(MKNetworkOperation *)op 420 | { 421 | // Sign and Enqueue the operation 422 | [self signRequest:op]; 423 | [self enqueueOperation:op]; 424 | } 425 | 426 | - (NSString *)generateXOAuthStringForURL:(NSString *)url method:(NSString *)method 427 | { 428 | NSAssert(_oAuthValues && self.consumerKey && self.consumerSecret, @"Please use an initializer with Consumer Key and Consumer Secret."); 429 | 430 | // Generate timestamp and nonce values 431 | [self setOAuthValue:[NSString stringWithFormat:@"%ld", time(NULL)] forKey:@"oauth_timestamp"]; 432 | [self setOAuthValue:[NSString uniqueString] forKey:@"oauth_nonce"]; 433 | 434 | // Construct the signature base string 435 | NSString *baseString = [self signatureBaseStringForURL:url method:method parameters:nil]; 436 | 437 | // Generate the signature 438 | switch (_signatureMethod) { 439 | case RSOAuthHMAC_SHA1: 440 | [self setOAuthValue:[self generateHMAC_SHA1SignatureFor:baseString] forKey:@"oauth_signature"]; 441 | break; 442 | default: 443 | [self setOAuthValue:[self generatePlaintextSignatureFor:baseString] forKey:@"oauth_signature"]; 444 | break; 445 | } 446 | 447 | NSMutableArray *oauthHeaders = [NSMutableArray array]; 448 | 449 | // Fill the authorization header array 450 | [_oAuthValues enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) { 451 | if (obj && ![obj isEqualToString:@""]) { 452 | [oauthHeaders addObject:[NSString stringWithFormat:@"%@=\"%@\"", [key mk_urlEncodedString], [obj mk_urlEncodedString]]]; 453 | } 454 | }]; 455 | 456 | // Set the XOAuth String 457 | NSString *xOAuthString = [NSString stringWithFormat:@"%@ %@ %@", [method uppercaseString], url, [oauthHeaders componentsJoinedByString:@","]]; 458 | 459 | // Base64-encode the string with no line wrap 460 | size_t outputLength; 461 | NSData *stringData = [xOAuthString dataUsingEncoding:NSUTF8StringEncoding]; 462 | char *outputBuffer = mk_NewBase64Encode([stringData bytes], [stringData length], false, &outputLength); 463 | NSString *finalString = [[NSString alloc] initWithBytes:outputBuffer length:outputLength encoding:NSASCIIStringEncoding]; 464 | free(outputBuffer); 465 | 466 | return finalString; 467 | } 468 | 469 | @end 470 | -------------------------------------------------------------------------------- /TwitterDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5B41240E19129582001F575F /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B41240D19129582001F575F /* Default-568h@2x.png */; }; 11 | 5B412410191295DE001F575F /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B41240F191295DE001F575F /* ImageIO.framework */; }; 12 | 5B9770C8149789C200791728 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B9770C7149789C200791728 /* UIKit.framework */; }; 13 | 5B9770CA149789C200791728 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B9770C9149789C200791728 /* Foundation.framework */; }; 14 | 5B9770CC149789C200791728 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B9770CB149789C200791728 /* CoreGraphics.framework */; }; 15 | 5B9770D2149789C200791728 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B9770D0149789C200791728 /* InfoPlist.strings */; }; 16 | 5B9770D4149789C200791728 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770D3149789C200791728 /* main.m */; }; 17 | 5B9770D8149789C200791728 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770D7149789C200791728 /* AppDelegate.m */; }; 18 | 5B9770DB149789C200791728 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770DA149789C200791728 /* ViewController.m */; }; 19 | 5B9770DE149789C200791728 /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B9770DC149789C200791728 /* ViewController.xib */; }; 20 | 5B97710614978D5400791728 /* NSDate+RFC1123.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770F614978D5400791728 /* NSDate+RFC1123.m */; }; 21 | 5B97710714978D5400791728 /* NSDictionary+RequestEncoding.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770F814978D5400791728 /* NSDictionary+RequestEncoding.m */; }; 22 | 5B97710814978D5400791728 /* NSString+MKNetworkKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770FA14978D5400791728 /* NSString+MKNetworkKitAdditions.m */; }; 23 | 5B97710914978D5400791728 /* UIAlertView+MKNetworkKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770FC14978D5400791728 /* UIAlertView+MKNetworkKitAdditions.m */; }; 24 | 5B97710A14978D5400791728 /* MKNetworkEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770FE14978D5400791728 /* MKNetworkEngine.m */; }; 25 | 5B97710B14978D5400791728 /* MKNetworkOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B97710114978D5400791728 /* MKNetworkOperation.m */; }; 26 | 5B97710C14978D5400791728 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B97710414978D5400791728 /* Reachability.m */; }; 27 | 5B97710E14978DB400791728 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B97710D14978DB400791728 /* CFNetwork.framework */; }; 28 | 5B97711014978DBC00791728 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B97710F14978DBC00791728 /* SystemConfiguration.framework */; }; 29 | 5B97711214978F6100791728 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B97711114978F6100791728 /* Security.framework */; }; 30 | 5B97711A14978FFD00791728 /* RSOAuthEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B97711814978FFD00791728 /* RSOAuthEngine.m */; }; 31 | 5B97711D1497911300791728 /* WebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B97711C1497911300791728 /* WebViewController.m */; }; 32 | 5B9771201497912A00791728 /* WebViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B97711E1497912A00791728 /* WebViewController.xib */; }; 33 | 5B9771241497927C00791728 /* RSTwitterEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9771231497927C00791728 /* RSTwitterEngine.m */; }; 34 | 5BAA64D61497D8CB00ED569D /* logo_cube_57.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BAA64D51497D8CB00ED569D /* logo_cube_57.png */; }; 35 | 5BAA64D81497D8D300ED569D /* logo_cube_114.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BAA64D71497D8D300ED569D /* logo_cube_114.png */; }; 36 | CD6AB1CD169D2F5100D2AD3F /* NSData+MKBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6AB1CC169D2F5100D2AD3F /* NSData+MKBase64.m */; }; 37 | /* End PBXBuildFile section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 5B41240D19129582001F575F /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; 41 | 5B41240F191295DE001F575F /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; }; 42 | 5B9770C3149789C200791728 /* TwitterDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TwitterDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 5B9770C7149789C200791728 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 44 | 5B9770C9149789C200791728 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 45 | 5B9770CB149789C200791728 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 46 | 5B9770CF149789C200791728 /* TwitterDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TwitterDemo-Info.plist"; sourceTree = ""; }; 47 | 5B9770D1149789C200791728 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 48 | 5B9770D3149789C200791728 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = main.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 49 | 5B9770D5149789C200791728 /* TwitterDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TwitterDemo-Prefix.pch"; sourceTree = ""; }; 50 | 5B9770D6149789C200791728 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = AppDelegate.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 51 | 5B9770D7149789C200791728 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AppDelegate.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 52 | 5B9770D9149789C200791728 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ViewController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 53 | 5B9770DA149789C200791728 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 54 | 5B9770DD149789C200791728 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/ViewController.xib; sourceTree = ""; }; 55 | 5B9770F514978D5400791728 /* NSDate+RFC1123.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+RFC1123.h"; sourceTree = ""; }; 56 | 5B9770F614978D5400791728 /* NSDate+RFC1123.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+RFC1123.m"; sourceTree = ""; }; 57 | 5B9770F714978D5400791728 /* NSDictionary+RequestEncoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+RequestEncoding.h"; sourceTree = ""; }; 58 | 5B9770F814978D5400791728 /* NSDictionary+RequestEncoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+RequestEncoding.m"; sourceTree = ""; }; 59 | 5B9770F914978D5400791728 /* NSString+MKNetworkKitAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MKNetworkKitAdditions.h"; sourceTree = ""; }; 60 | 5B9770FA14978D5400791728 /* NSString+MKNetworkKitAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MKNetworkKitAdditions.m"; sourceTree = ""; }; 61 | 5B9770FB14978D5400791728 /* UIAlertView+MKNetworkKitAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIAlertView+MKNetworkKitAdditions.h"; sourceTree = ""; }; 62 | 5B9770FC14978D5400791728 /* UIAlertView+MKNetworkKitAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIAlertView+MKNetworkKitAdditions.m"; sourceTree = ""; }; 63 | 5B9770FD14978D5400791728 /* MKNetworkEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MKNetworkEngine.h; sourceTree = ""; }; 64 | 5B9770FE14978D5400791728 /* MKNetworkEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MKNetworkEngine.m; sourceTree = ""; }; 65 | 5B9770FF14978D5400791728 /* MKNetworkKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MKNetworkKit.h; sourceTree = ""; }; 66 | 5B97710014978D5400791728 /* MKNetworkOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MKNetworkOperation.h; sourceTree = ""; }; 67 | 5B97710114978D5400791728 /* MKNetworkOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MKNetworkOperation.m; sourceTree = ""; }; 68 | 5B97710314978D5400791728 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; 69 | 5B97710414978D5400791728 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; 70 | 5B97710D14978DB400791728 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 71 | 5B97710F14978DBC00791728 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 72 | 5B97711114978F6100791728 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 73 | 5B97711714978FFD00791728 /* RSOAuthEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RSOAuthEngine.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 74 | 5B97711814978FFD00791728 /* RSOAuthEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RSOAuthEngine.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 75 | 5B97711B1497911300791728 /* WebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = WebViewController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 76 | 5B97711C1497911300791728 /* WebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewController.m; sourceTree = ""; }; 77 | 5B97711F1497912A00791728 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/WebViewController.xib; sourceTree = ""; }; 78 | 5B9771221497927C00791728 /* RSTwitterEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RSTwitterEngine.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 79 | 5B9771231497927C00791728 /* RSTwitterEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RSTwitterEngine.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 80 | 5BAA64D51497D8CB00ED569D /* logo_cube_57.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logo_cube_57.png; path = ../logo_cube_57.png; sourceTree = ""; }; 81 | 5BAA64D71497D8D300ED569D /* logo_cube_114.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logo_cube_114.png; path = ../logo_cube_114.png; sourceTree = ""; }; 82 | CD6AB1CB169D2F5100D2AD3F /* NSData+MKBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+MKBase64.h"; sourceTree = ""; }; 83 | CD6AB1CC169D2F5100D2AD3F /* NSData+MKBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+MKBase64.m"; sourceTree = ""; }; 84 | /* End PBXFileReference section */ 85 | 86 | /* Begin PBXFrameworksBuildPhase section */ 87 | 5B9770C0149789C200791728 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | 5B412410191295DE001F575F /* ImageIO.framework in Frameworks */, 92 | 5B97711214978F6100791728 /* Security.framework in Frameworks */, 93 | 5B97711014978DBC00791728 /* SystemConfiguration.framework in Frameworks */, 94 | 5B97710E14978DB400791728 /* CFNetwork.framework in Frameworks */, 95 | 5B9770C8149789C200791728 /* UIKit.framework in Frameworks */, 96 | 5B9770CA149789C200791728 /* Foundation.framework in Frameworks */, 97 | 5B9770CC149789C200791728 /* CoreGraphics.framework in Frameworks */, 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | /* End PBXFrameworksBuildPhase section */ 102 | 103 | /* Begin PBXGroup section */ 104 | 5B9770B8149789C200791728 = { 105 | isa = PBXGroup; 106 | children = ( 107 | 5B9770F014978CC700791728 /* MKNetworkKit */, 108 | 5B97711314978FD300791728 /* RSOAuthEngine */, 109 | 5B9770CD149789C200791728 /* TwitterDemo */, 110 | 5B9770C6149789C200791728 /* Frameworks */, 111 | 5B9770C4149789C200791728 /* Products */, 112 | ); 113 | sourceTree = ""; 114 | }; 115 | 5B9770C4149789C200791728 /* Products */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 5B9770C3149789C200791728 /* TwitterDemo.app */, 119 | ); 120 | name = Products; 121 | sourceTree = ""; 122 | }; 123 | 5B9770C6149789C200791728 /* Frameworks */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 5B41240F191295DE001F575F /* ImageIO.framework */, 127 | 5B97711114978F6100791728 /* Security.framework */, 128 | 5B97710F14978DBC00791728 /* SystemConfiguration.framework */, 129 | 5B97710D14978DB400791728 /* CFNetwork.framework */, 130 | 5B9770C7149789C200791728 /* UIKit.framework */, 131 | 5B9770C9149789C200791728 /* Foundation.framework */, 132 | 5B9770CB149789C200791728 /* CoreGraphics.framework */, 133 | ); 134 | name = Frameworks; 135 | sourceTree = ""; 136 | }; 137 | 5B9770CD149789C200791728 /* TwitterDemo */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 5B9771211497926200791728 /* Twitter */, 141 | 5B9770D6149789C200791728 /* AppDelegate.h */, 142 | 5B9770D7149789C200791728 /* AppDelegate.m */, 143 | 5B9770D9149789C200791728 /* ViewController.h */, 144 | 5B9770DA149789C200791728 /* ViewController.m */, 145 | 5B9770DC149789C200791728 /* ViewController.xib */, 146 | 5B97711B1497911300791728 /* WebViewController.h */, 147 | 5B97711C1497911300791728 /* WebViewController.m */, 148 | 5B97711E1497912A00791728 /* WebViewController.xib */, 149 | 5B9770CE149789C200791728 /* Supporting Files */, 150 | ); 151 | path = TwitterDemo; 152 | sourceTree = ""; 153 | }; 154 | 5B9770CE149789C200791728 /* Supporting Files */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 5B41240D19129582001F575F /* Default-568h@2x.png */, 158 | 5BAA64D71497D8D300ED569D /* logo_cube_114.png */, 159 | 5BAA64D51497D8CB00ED569D /* logo_cube_57.png */, 160 | 5B9770CF149789C200791728 /* TwitterDemo-Info.plist */, 161 | 5B9770D0149789C200791728 /* InfoPlist.strings */, 162 | 5B9770D3149789C200791728 /* main.m */, 163 | 5B9770D5149789C200791728 /* TwitterDemo-Prefix.pch */, 164 | ); 165 | name = "Supporting Files"; 166 | sourceTree = ""; 167 | }; 168 | 5B9770F014978CC700791728 /* MKNetworkKit */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 5B9770F214978D5400791728 /* Categories */, 172 | 5B97710214978D5400791728 /* Reachability */, 173 | 5B9770FD14978D5400791728 /* MKNetworkEngine.h */, 174 | 5B9770FE14978D5400791728 /* MKNetworkEngine.m */, 175 | 5B9770FF14978D5400791728 /* MKNetworkKit.h */, 176 | 5B97710014978D5400791728 /* MKNetworkOperation.h */, 177 | 5B97710114978D5400791728 /* MKNetworkOperation.m */, 178 | ); 179 | name = MKNetworkKit; 180 | path = MKNetworkKit/MKNetworkKit; 181 | sourceTree = SOURCE_ROOT; 182 | }; 183 | 5B9770F214978D5400791728 /* Categories */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | CD6AB1CB169D2F5100D2AD3F /* NSData+MKBase64.h */, 187 | CD6AB1CC169D2F5100D2AD3F /* NSData+MKBase64.m */, 188 | 5B9770F514978D5400791728 /* NSDate+RFC1123.h */, 189 | 5B9770F614978D5400791728 /* NSDate+RFC1123.m */, 190 | 5B9770F714978D5400791728 /* NSDictionary+RequestEncoding.h */, 191 | 5B9770F814978D5400791728 /* NSDictionary+RequestEncoding.m */, 192 | 5B9770F914978D5400791728 /* NSString+MKNetworkKitAdditions.h */, 193 | 5B9770FA14978D5400791728 /* NSString+MKNetworkKitAdditions.m */, 194 | 5B9770FB14978D5400791728 /* UIAlertView+MKNetworkKitAdditions.h */, 195 | 5B9770FC14978D5400791728 /* UIAlertView+MKNetworkKitAdditions.m */, 196 | ); 197 | path = Categories; 198 | sourceTree = ""; 199 | }; 200 | 5B97710214978D5400791728 /* Reachability */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | 5B97710314978D5400791728 /* Reachability.h */, 204 | 5B97710414978D5400791728 /* Reachability.m */, 205 | ); 206 | path = Reachability; 207 | sourceTree = ""; 208 | }; 209 | 5B97711314978FD300791728 /* RSOAuthEngine */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | 5B97711714978FFD00791728 /* RSOAuthEngine.h */, 213 | 5B97711814978FFD00791728 /* RSOAuthEngine.m */, 214 | ); 215 | path = RSOAuthEngine; 216 | sourceTree = SOURCE_ROOT; 217 | }; 218 | 5B9771211497926200791728 /* Twitter */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 5B9771221497927C00791728 /* RSTwitterEngine.h */, 222 | 5B9771231497927C00791728 /* RSTwitterEngine.m */, 223 | ); 224 | path = Twitter; 225 | sourceTree = ""; 226 | }; 227 | /* End PBXGroup section */ 228 | 229 | /* Begin PBXNativeTarget section */ 230 | 5B9770C2149789C200791728 /* TwitterDemo */ = { 231 | isa = PBXNativeTarget; 232 | buildConfigurationList = 5B9770E1149789C200791728 /* Build configuration list for PBXNativeTarget "TwitterDemo" */; 233 | buildPhases = ( 234 | 5B9770BF149789C200791728 /* Sources */, 235 | 5B9770C0149789C200791728 /* Frameworks */, 236 | 5B9770C1149789C200791728 /* Resources */, 237 | ); 238 | buildRules = ( 239 | ); 240 | dependencies = ( 241 | ); 242 | name = TwitterDemo; 243 | productName = TwitterDemo; 244 | productReference = 5B9770C3149789C200791728 /* TwitterDemo.app */; 245 | productType = "com.apple.product-type.application"; 246 | }; 247 | /* End PBXNativeTarget section */ 248 | 249 | /* Begin PBXProject section */ 250 | 5B9770BA149789C200791728 /* Project object */ = { 251 | isa = PBXProject; 252 | attributes = { 253 | LastUpgradeCheck = 0510; 254 | ORGANIZATIONNAME = SharpCube; 255 | }; 256 | buildConfigurationList = 5B9770BD149789C200791728 /* Build configuration list for PBXProject "TwitterDemo" */; 257 | compatibilityVersion = "Xcode 3.2"; 258 | developmentRegion = English; 259 | hasScannedForEncodings = 0; 260 | knownRegions = ( 261 | en, 262 | ); 263 | mainGroup = 5B9770B8149789C200791728; 264 | productRefGroup = 5B9770C4149789C200791728 /* Products */; 265 | projectDirPath = ""; 266 | projectRoot = ""; 267 | targets = ( 268 | 5B9770C2149789C200791728 /* TwitterDemo */, 269 | ); 270 | }; 271 | /* End PBXProject section */ 272 | 273 | /* Begin PBXResourcesBuildPhase section */ 274 | 5B9770C1149789C200791728 /* Resources */ = { 275 | isa = PBXResourcesBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | 5B9770D2149789C200791728 /* InfoPlist.strings in Resources */, 279 | 5B9770DE149789C200791728 /* ViewController.xib in Resources */, 280 | 5B9771201497912A00791728 /* WebViewController.xib in Resources */, 281 | 5BAA64D61497D8CB00ED569D /* logo_cube_57.png in Resources */, 282 | 5BAA64D81497D8D300ED569D /* logo_cube_114.png in Resources */, 283 | 5B41240E19129582001F575F /* Default-568h@2x.png in Resources */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | /* End PBXResourcesBuildPhase section */ 288 | 289 | /* Begin PBXSourcesBuildPhase section */ 290 | 5B9770BF149789C200791728 /* Sources */ = { 291 | isa = PBXSourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | 5B9770D4149789C200791728 /* main.m in Sources */, 295 | 5B9770D8149789C200791728 /* AppDelegate.m in Sources */, 296 | 5B9770DB149789C200791728 /* ViewController.m in Sources */, 297 | 5B97710614978D5400791728 /* NSDate+RFC1123.m in Sources */, 298 | 5B97710714978D5400791728 /* NSDictionary+RequestEncoding.m in Sources */, 299 | 5B97710814978D5400791728 /* NSString+MKNetworkKitAdditions.m in Sources */, 300 | 5B97710914978D5400791728 /* UIAlertView+MKNetworkKitAdditions.m in Sources */, 301 | 5B97710A14978D5400791728 /* MKNetworkEngine.m in Sources */, 302 | 5B97710B14978D5400791728 /* MKNetworkOperation.m in Sources */, 303 | 5B97710C14978D5400791728 /* Reachability.m in Sources */, 304 | 5B97711A14978FFD00791728 /* RSOAuthEngine.m in Sources */, 305 | 5B97711D1497911300791728 /* WebViewController.m in Sources */, 306 | 5B9771241497927C00791728 /* RSTwitterEngine.m in Sources */, 307 | CD6AB1CD169D2F5100D2AD3F /* NSData+MKBase64.m in Sources */, 308 | ); 309 | runOnlyForDeploymentPostprocessing = 0; 310 | }; 311 | /* End PBXSourcesBuildPhase section */ 312 | 313 | /* Begin PBXVariantGroup section */ 314 | 5B9770D0149789C200791728 /* InfoPlist.strings */ = { 315 | isa = PBXVariantGroup; 316 | children = ( 317 | 5B9770D1149789C200791728 /* en */, 318 | ); 319 | name = InfoPlist.strings; 320 | sourceTree = ""; 321 | }; 322 | 5B9770DC149789C200791728 /* ViewController.xib */ = { 323 | isa = PBXVariantGroup; 324 | children = ( 325 | 5B9770DD149789C200791728 /* en */, 326 | ); 327 | name = ViewController.xib; 328 | sourceTree = ""; 329 | }; 330 | 5B97711E1497912A00791728 /* WebViewController.xib */ = { 331 | isa = PBXVariantGroup; 332 | children = ( 333 | 5B97711F1497912A00791728 /* en */, 334 | ); 335 | name = WebViewController.xib; 336 | sourceTree = ""; 337 | }; 338 | /* End PBXVariantGroup section */ 339 | 340 | /* Begin XCBuildConfiguration section */ 341 | 5B9770DF149789C200791728 /* Debug */ = { 342 | isa = XCBuildConfiguration; 343 | buildSettings = { 344 | ALWAYS_SEARCH_USER_PATHS = NO; 345 | CLANG_ENABLE_OBJC_ARC = YES; 346 | CLANG_WARN_BOOL_CONVERSION = YES; 347 | CLANG_WARN_CONSTANT_CONVERSION = YES; 348 | CLANG_WARN_EMPTY_BODY = YES; 349 | CLANG_WARN_ENUM_CONVERSION = YES; 350 | CLANG_WARN_INT_CONVERSION = YES; 351 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 352 | CODE_SIGN_IDENTITY = "iPhone Developer"; 353 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 354 | COPY_PHASE_STRIP = NO; 355 | GCC_C_LANGUAGE_STANDARD = gnu99; 356 | GCC_DYNAMIC_NO_PIC = NO; 357 | GCC_OPTIMIZATION_LEVEL = 0; 358 | GCC_PREPROCESSOR_DEFINITIONS = ( 359 | "DEBUG=1", 360 | "$(inherited)", 361 | ); 362 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 363 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 364 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 365 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 366 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 367 | GCC_WARN_UNDECLARED_SELECTOR = YES; 368 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 369 | GCC_WARN_UNUSED_FUNCTION = YES; 370 | GCC_WARN_UNUSED_VARIABLE = YES; 371 | IPHONEOS_DEPLOYMENT_TARGET = 4.3; 372 | ONLY_ACTIVE_ARCH = YES; 373 | PROVISIONING_PROFILE = ""; 374 | "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; 375 | SDKROOT = iphoneos; 376 | }; 377 | name = Debug; 378 | }; 379 | 5B9770E0149789C200791728 /* Release */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | ALWAYS_SEARCH_USER_PATHS = NO; 383 | CLANG_ENABLE_OBJC_ARC = YES; 384 | CLANG_WARN_BOOL_CONVERSION = YES; 385 | CLANG_WARN_CONSTANT_CONVERSION = YES; 386 | CLANG_WARN_EMPTY_BODY = YES; 387 | CLANG_WARN_ENUM_CONVERSION = YES; 388 | CLANG_WARN_INT_CONVERSION = YES; 389 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 390 | CODE_SIGN_IDENTITY = "iPhone Developer"; 391 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 392 | COPY_PHASE_STRIP = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu99; 394 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 397 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 398 | GCC_WARN_UNDECLARED_SELECTOR = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 400 | GCC_WARN_UNUSED_FUNCTION = YES; 401 | GCC_WARN_UNUSED_VARIABLE = YES; 402 | IPHONEOS_DEPLOYMENT_TARGET = 4.3; 403 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 404 | PROVISIONING_PROFILE = ""; 405 | "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; 406 | SDKROOT = iphoneos; 407 | VALIDATE_PRODUCT = YES; 408 | }; 409 | name = Release; 410 | }; 411 | 5B9770E2149789C200791728 /* Debug */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 415 | GCC_PREFIX_HEADER = "TwitterDemo/TwitterDemo-Prefix.pch"; 416 | INFOPLIST_FILE = "TwitterDemo/TwitterDemo-Info.plist"; 417 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 418 | PRODUCT_NAME = "$(TARGET_NAME)"; 419 | WRAPPER_EXTENSION = app; 420 | }; 421 | name = Debug; 422 | }; 423 | 5B9770E3149789C200791728 /* Release */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 427 | GCC_PREFIX_HEADER = "TwitterDemo/TwitterDemo-Prefix.pch"; 428 | INFOPLIST_FILE = "TwitterDemo/TwitterDemo-Info.plist"; 429 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 430 | PRODUCT_NAME = "$(TARGET_NAME)"; 431 | WRAPPER_EXTENSION = app; 432 | }; 433 | name = Release; 434 | }; 435 | /* End XCBuildConfiguration section */ 436 | 437 | /* Begin XCConfigurationList section */ 438 | 5B9770BD149789C200791728 /* Build configuration list for PBXProject "TwitterDemo" */ = { 439 | isa = XCConfigurationList; 440 | buildConfigurations = ( 441 | 5B9770DF149789C200791728 /* Debug */, 442 | 5B9770E0149789C200791728 /* Release */, 443 | ); 444 | defaultConfigurationIsVisible = 0; 445 | defaultConfigurationName = Release; 446 | }; 447 | 5B9770E1149789C200791728 /* Build configuration list for PBXNativeTarget "TwitterDemo" */ = { 448 | isa = XCConfigurationList; 449 | buildConfigurations = ( 450 | 5B9770E2149789C200791728 /* Debug */, 451 | 5B9770E3149789C200791728 /* Release */, 452 | ); 453 | defaultConfigurationIsVisible = 0; 454 | defaultConfigurationName = Release; 455 | }; 456 | /* End XCConfigurationList section */ 457 | }; 458 | rootObject = 5B9770BA149789C200791728 /* Project object */; 459 | } 460 | -------------------------------------------------------------------------------- /TwitterDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TwitterDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TwitterDemo 4 | // 5 | // Created by Rodrigo Sieiro on 12/13/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | @interface AppDelegate : UIResponder 29 | 30 | @property (strong, nonatomic) UIWindow *window; 31 | @property (strong, nonatomic) UINavigationController *viewController; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /TwitterDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TwitterDemo 4 | // 5 | // Created by Rodrigo Sieiro on 12/13/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "AppDelegate.h" 27 | #import "ViewController.h" 28 | 29 | @implementation AppDelegate 30 | 31 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 32 | { 33 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 34 | 35 | ViewController *vc = [ViewController new]; 36 | self.viewController = [[UINavigationController alloc] initWithRootViewController:vc]; 37 | self.window.rootViewController = self.viewController; 38 | [self.window makeKeyAndVisible]; 39 | return YES; 40 | } 41 | 42 | - (void)applicationWillResignActive:(UIApplication *)application 43 | { 44 | /* 45 | Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 46 | Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 47 | */ 48 | } 49 | 50 | - (void)applicationDidEnterBackground:(UIApplication *)application 51 | { 52 | /* 53 | Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 54 | If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 55 | */ 56 | } 57 | 58 | - (void)applicationWillEnterForeground:(UIApplication *)application 59 | { 60 | /* 61 | Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 62 | */ 63 | } 64 | 65 | - (void)applicationDidBecomeActive:(UIApplication *)application 66 | { 67 | /* 68 | Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 69 | */ 70 | } 71 | 72 | - (void)applicationWillTerminate:(UIApplication *)application 73 | { 74 | /* 75 | Called when the application is about to terminate. 76 | Save data if appropriate. 77 | See also applicationDidEnterBackground:. 78 | */ 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /TwitterDemo/Twitter/RSTwitterEngine.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSTwitterEngine.h 3 | // RSOAuthEngine 4 | // 5 | // Created by Rodrigo Sieiro on 12/8/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "RSOAuthEngine.h" 27 | 28 | @protocol RSTwitterEngineDelegate; 29 | 30 | typedef void (^RSTwitterEngineCompletionBlock)(NSError *error); 31 | 32 | @interface RSTwitterEngine : RSOAuthEngine 33 | { 34 | RSTwitterEngineCompletionBlock _oAuthCompletionBlock; 35 | NSString *_screenName; 36 | } 37 | 38 | @property (assign) id delegate; 39 | @property (readonly) NSString *screenName; 40 | 41 | - (id)initWithDelegate:(id )delegate; 42 | - (void)authenticateWithCompletionBlock:(RSTwitterEngineCompletionBlock)completionBlock; 43 | - (void)resumeAuthenticationFlowWithURL:(NSURL *)url; 44 | - (void)cancelAuthentication; 45 | - (void)forgetStoredToken; 46 | - (void)sendTweet:(NSString *)tweet withCompletionBlock:(RSTwitterEngineCompletionBlock)completionBlock; 47 | 48 | @end 49 | 50 | @protocol RSTwitterEngineDelegate 51 | 52 | - (void)twitterEngine:(RSTwitterEngine *)engine needsToOpenURL:(NSURL *)url; 53 | - (void)twitterEngine:(RSTwitterEngine *)engine statusUpdate:(NSString *)message; 54 | 55 | @end -------------------------------------------------------------------------------- /TwitterDemo/Twitter/RSTwitterEngine.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSTwitterEngine.m 3 | // RSOAuthEngine 4 | // 5 | // Created by Rodrigo Sieiro on 12/8/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "RSTwitterEngine.h" 27 | 28 | // Never share this information 29 | #error Put your Consumer Key and Secret here, then remove this error 30 | #define TW_CONSUMER_KEY @"" 31 | #define TW_CONSUMER_SECRET @"" 32 | 33 | // This will be called after the user authorizes your app 34 | #define TW_CALLBACK_URL @"rstwitterengine://auth_token" 35 | 36 | // Default twitter hostname and paths 37 | #define TW_HOSTNAME @"api.twitter.com" 38 | #define TW_REQUEST_TOKEN @"oauth/request_token" 39 | #define TW_ACCESS_TOKEN @"oauth/access_token" 40 | #define TW_STATUS_UPDATE @"1.1/statuses/update.json" 41 | 42 | // URL to redirect the user for authentication 43 | #define TW_AUTHORIZE(__TOKEN__) [NSString stringWithFormat:@"https://api.twitter.com/oauth/authorize?oauth_token=%@", __TOKEN__] 44 | 45 | @interface RSTwitterEngine () 46 | 47 | - (void)removeOAuthTokenFromKeychain; 48 | - (void)storeOAuthTokenInKeychain; 49 | - (void)retrieveOAuthTokenFromKeychain; 50 | 51 | @end 52 | 53 | @implementation RSTwitterEngine 54 | 55 | #pragma mark - Read-only Properties 56 | 57 | - (NSString *)screenName 58 | { 59 | return _screenName; 60 | } 61 | 62 | #pragma mark - Initialization 63 | 64 | - (id)initWithDelegate:(id )delegate 65 | { 66 | self = [super initWithHostName:TW_HOSTNAME 67 | customHeaderFields:nil 68 | signatureMethod:RSOAuthHMAC_SHA1 69 | consumerKey:TW_CONSUMER_KEY 70 | consumerSecret:TW_CONSUMER_SECRET 71 | callbackURL:TW_CALLBACK_URL]; 72 | 73 | if (self) { 74 | _oAuthCompletionBlock = nil; 75 | _screenName = nil; 76 | self.delegate = delegate; 77 | 78 | // Retrieve OAuth access token (if previously stored) 79 | [self retrieveOAuthTokenFromKeychain]; 80 | } 81 | 82 | return self; 83 | } 84 | 85 | #pragma mark - OAuth Access Token store/retrieve 86 | 87 | - (void)removeOAuthTokenFromKeychain 88 | { 89 | // Build the keychain query 90 | NSMutableDictionary *keychainQuery = [NSMutableDictionary dictionaryWithObjectsAndKeys: 91 | (__bridge_transfer NSString *)kSecClassGenericPassword, (__bridge_transfer NSString *)kSecClass, 92 | self.consumerKey, kSecAttrService, 93 | self.consumerKey, kSecAttrAccount, 94 | kCFBooleanTrue, kSecReturnAttributes, 95 | nil]; 96 | 97 | // If there's a token stored for this user, delete it 98 | CFDictionaryRef query = (__bridge_retained CFDictionaryRef) keychainQuery; 99 | SecItemDelete(query); 100 | CFRelease(query); 101 | } 102 | 103 | - (void)storeOAuthTokenInKeychain 104 | { 105 | // Build the keychain query 106 | NSMutableDictionary *keychainQuery = [NSMutableDictionary dictionaryWithObjectsAndKeys: 107 | (__bridge_transfer NSString *)kSecClassGenericPassword, (__bridge_transfer NSString *)kSecClass, 108 | self.consumerKey, kSecAttrService, 109 | self.consumerKey, kSecAttrAccount, 110 | kCFBooleanTrue, kSecReturnAttributes, 111 | nil]; 112 | 113 | CFTypeRef resData = NULL; 114 | 115 | // If there's a token stored for this user, delete it first 116 | CFDictionaryRef query = (__bridge_retained CFDictionaryRef) keychainQuery; 117 | SecItemDelete(query); 118 | CFRelease(query); 119 | 120 | // Build the token dictionary 121 | NSMutableDictionary *tokenDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys: 122 | self.token, @"oauth_token", 123 | self.tokenSecret, @"oauth_token_secret", 124 | self.screenName, @"screen_name", 125 | nil]; 126 | 127 | // Add the token dictionary to the query 128 | [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:tokenDictionary] 129 | forKey:(__bridge_transfer NSString *)kSecValueData]; 130 | 131 | // Add the token data to the keychain 132 | // Even if we never use resData, replacing with NULL in the call throws EXC_BAD_ACCESS 133 | query = (__bridge_retained CFDictionaryRef) keychainQuery; 134 | SecItemAdd(query, (CFTypeRef *) &resData); 135 | CFRelease(query); 136 | } 137 | 138 | - (void)retrieveOAuthTokenFromKeychain 139 | { 140 | // Build the keychain query 141 | NSMutableDictionary *keychainQuery = [NSMutableDictionary dictionaryWithObjectsAndKeys: 142 | (__bridge_transfer NSString *)kSecClassGenericPassword, (__bridge_transfer NSString *)kSecClass, 143 | self.consumerKey, kSecAttrService, 144 | self.consumerKey, kSecAttrAccount, 145 | kCFBooleanTrue, kSecReturnData, 146 | kSecMatchLimitOne, kSecMatchLimit, 147 | nil]; 148 | 149 | // Get the token data from the keychain 150 | CFTypeRef resData = NULL; 151 | 152 | // Get the token dictionary from the keychain 153 | CFDictionaryRef query = (__bridge_retained CFDictionaryRef) keychainQuery; 154 | 155 | if (SecItemCopyMatching(query, (CFTypeRef *) &resData) == noErr) 156 | { 157 | NSData *resultData = (__bridge_transfer NSData *)resData; 158 | 159 | if (resultData) 160 | { 161 | NSMutableDictionary *tokenDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:resultData]; 162 | 163 | if (tokenDictionary) { 164 | [self setAccessToken:[tokenDictionary objectForKey:@"oauth_token"] 165 | secret:[tokenDictionary objectForKey:@"oauth_token_secret"]]; 166 | 167 | _screenName = [tokenDictionary objectForKey:@"screen_name"]; 168 | } 169 | } 170 | } 171 | 172 | CFRelease(query); 173 | } 174 | 175 | #pragma mark - OAuth Authentication Flow 176 | 177 | - (void)authenticateWithCompletionBlock:(RSTwitterEngineCompletionBlock)completionBlock 178 | { 179 | // Store the Completion Block to call after Authenticated 180 | _oAuthCompletionBlock = [completionBlock copy]; 181 | 182 | // First we reset the OAuth token, so we won't send previous tokens in the request 183 | [self resetOAuthToken]; 184 | 185 | // OAuth Step 1 - Obtain a request token 186 | MKNetworkOperation *op = [self operationWithPath:TW_REQUEST_TOKEN 187 | params:nil 188 | httpMethod:@"POST" 189 | ssl:YES]; 190 | 191 | [op addCompletionHandler:^(MKNetworkOperation *completedOperation) { 192 | // Fill the request token with the returned data 193 | [self fillTokenWithResponseBody:[completedOperation responseString] type:RSOAuthRequestToken]; 194 | 195 | // OAuth Step 2 - Redirect user to authorization page 196 | [self.delegate twitterEngine:self statusUpdate:@"Waiting for user authorization..."]; 197 | NSURL *url = [NSURL URLWithString:TW_AUTHORIZE(self.token)]; 198 | [self.delegate twitterEngine:self needsToOpenURL:url]; 199 | } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) { 200 | completionBlock(error); 201 | _oAuthCompletionBlock = nil; 202 | }]; 203 | 204 | [self.delegate twitterEngine:self statusUpdate:@"Requesting Tokens..."]; 205 | [self enqueueSignedOperation:op]; 206 | } 207 | 208 | - (void)resumeAuthenticationFlowWithURL:(NSURL *)url 209 | { 210 | // Fill the request token with data returned in the callback URL 211 | [self fillTokenWithResponseBody:url.query type:RSOAuthRequestToken]; 212 | 213 | // OAuth Step 3 - Exchange the request token with an access token 214 | MKNetworkOperation *op = [self operationWithPath:TW_ACCESS_TOKEN 215 | params:nil 216 | httpMethod:@"POST" 217 | ssl:YES]; 218 | 219 | [op addCompletionHandler:^(MKNetworkOperation *completedOperation) { 220 | // Fill the access token with the returned data 221 | [self fillTokenWithResponseBody:[completedOperation responseString] type:RSOAuthAccessToken]; 222 | 223 | // Retrieve the user's screen name 224 | _screenName = [self customValueForKey:@"screen_name"]; 225 | 226 | // Store the OAuth access token 227 | [self storeOAuthTokenInKeychain]; 228 | 229 | // Finished, return to previous method 230 | if (_oAuthCompletionBlock) _oAuthCompletionBlock(nil); 231 | _oAuthCompletionBlock = nil; 232 | } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) { 233 | if (_oAuthCompletionBlock) _oAuthCompletionBlock(error); 234 | _oAuthCompletionBlock = nil; 235 | }]; 236 | 237 | [self.delegate twitterEngine:self statusUpdate:@"Authenticating..."]; 238 | [self enqueueSignedOperation:op]; 239 | } 240 | 241 | - (void)cancelAuthentication 242 | { 243 | NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:@"Authentication cancelled.", NSLocalizedDescriptionKey, nil]; 244 | NSError *error = [NSError errorWithDomain:@"com.sharpcube.RSTwitterEngine.ErrorDomain" code:401 userInfo:ui]; 245 | 246 | if (_oAuthCompletionBlock) _oAuthCompletionBlock(error); 247 | _oAuthCompletionBlock = nil; 248 | } 249 | 250 | - (void)forgetStoredToken 251 | { 252 | [self removeOAuthTokenFromKeychain]; 253 | 254 | [self resetOAuthToken]; 255 | _screenName = nil; 256 | } 257 | 258 | #pragma mark - Public Methods 259 | 260 | - (void)sendTweet:(NSString *)tweet withCompletionBlock:(RSTwitterEngineCompletionBlock)completionBlock 261 | { 262 | if (!self.isAuthenticated) { 263 | [self authenticateWithCompletionBlock:^(NSError *error) { 264 | if (error) { 265 | // Authentication failed, return the error 266 | completionBlock(error); 267 | } else { 268 | // Authentication succeeded, call this method again 269 | [self sendTweet:tweet withCompletionBlock:completionBlock]; 270 | } 271 | }]; 272 | 273 | // This method will be called again once the authentication completes 274 | return; 275 | } 276 | 277 | // Fill the post body with the tweet 278 | NSMutableDictionary *postParams = [NSMutableDictionary dictionaryWithObjectsAndKeys: 279 | tweet, @"status", 280 | nil]; 281 | 282 | // If the user marks the option "HTTPS Only" in his/her profile, 283 | // Twitter will fail all non-auth requests that use only HTTP 284 | // with a misleading "OAuth error". I guess it's a bug. 285 | MKNetworkOperation *op = [self operationWithPath:TW_STATUS_UPDATE 286 | params:postParams 287 | httpMethod:@"POST" 288 | ssl:YES]; 289 | 290 | [op addCompletionHandler:^(MKNetworkOperation *completedOperation) { 291 | completionBlock(nil); 292 | } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) { 293 | completionBlock(error); 294 | }]; 295 | 296 | [self.delegate twitterEngine:self statusUpdate:@"Sending tweet..."]; 297 | [self enqueueSignedOperation:op]; 298 | } 299 | 300 | @end 301 | -------------------------------------------------------------------------------- /TwitterDemo/TwitterDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | QuickTweet! 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFiles 12 | 13 | logo_cube_57.png 14 | logo_cube_114.png 15 | 16 | CFBundleIcons 17 | 18 | CFBundlePrimaryIcon 19 | 20 | CFBundleIconFiles 21 | 22 | logo_cube_57.png 23 | logo_cube_114.png 24 | 25 | UIPrerenderedIcon 26 | 27 | 28 | 29 | CFBundleIdentifier 30 | com.sharpcube.${PRODUCT_NAME:rfc1034identifier} 31 | CFBundleInfoDictionaryVersion 32 | 6.0 33 | CFBundleName 34 | ${PRODUCT_NAME} 35 | CFBundlePackageType 36 | APPL 37 | CFBundleShortVersionString 38 | 1.0 39 | CFBundleSignature 40 | ???? 41 | CFBundleVersion 42 | 1.0 43 | LSRequiresIPhoneOS 44 | 45 | UIRequiredDeviceCapabilities 46 | 47 | armv7 48 | 49 | UISupportedInterfaceOrientations 50 | 51 | UIInterfaceOrientationPortrait 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /TwitterDemo/TwitterDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'TwitterDemo' target in the 'TwitterDemo' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #import "MKNetworkKit.h" 15 | #endif 16 | -------------------------------------------------------------------------------- /TwitterDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TwitterDemo 4 | // 5 | // Created by Rodrigo Sieiro on 12/13/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | #import "RSTwitterEngine.h" 28 | #import "WebViewController.h" 29 | 30 | @interface ViewController : UIViewController 31 | 32 | @property (strong, nonatomic) RSTwitterEngine *twitterEngine; 33 | 34 | @property (weak, nonatomic) IBOutlet UITextView *textView; 35 | @property (weak, nonatomic) IBOutlet UILabel *statusLabel; 36 | 37 | - (IBAction)sendTweet:(id)sender; 38 | - (void)swipedRight:(UIGestureRecognizer *)recognizer; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /TwitterDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TwitterDemo 4 | // 5 | // Created by Rodrigo Sieiro on 12/13/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "ViewController.h" 27 | 28 | @implementation ViewController 29 | 30 | - (void)didReceiveMemoryWarning 31 | { 32 | [super didReceiveMemoryWarning]; 33 | } 34 | 35 | #pragma mark - View Lifecycle 36 | 37 | - (void)viewDidLoad 38 | { 39 | [super viewDidLoad]; 40 | 41 | if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) { 42 | self.edgesForExtendedLayout = UIRectEdgeNone; 43 | } 44 | 45 | self.title = @"QuickTweet!"; 46 | 47 | UIBarButtonItem *send = [[UIBarButtonItem alloc] initWithTitle:@"Send" style:UIBarButtonItemStyleDone target:self action:@selector(sendTweet:)]; 48 | self.navigationItem.rightBarButtonItem = send; 49 | 50 | self.twitterEngine = [[RSTwitterEngine alloc] initWithDelegate:self]; 51 | 52 | // A right swipe on the status label will clear the stored token 53 | UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipedRight:)]; 54 | swipeRight.direction = UISwipeGestureRecognizerDirectionRight; 55 | swipeRight.numberOfTouchesRequired = 1; 56 | [self.statusLabel.superview addGestureRecognizer:swipeRight]; 57 | 58 | // Check if the user is already authenticated 59 | if (self.twitterEngine.isAuthenticated) { 60 | self.statusLabel.text = [NSString stringWithFormat:@"Signed in as @%@.", self.twitterEngine.screenName]; 61 | } else { 62 | self.statusLabel.text = @"Not signed in."; 63 | } 64 | 65 | [self.textView becomeFirstResponder]; 66 | } 67 | 68 | - (void)viewDidUnload 69 | { 70 | [self setTextView:nil]; 71 | [self setTwitterEngine:nil]; 72 | [self setStatusLabel:nil]; 73 | 74 | [super viewDidUnload]; 75 | } 76 | 77 | - (void)viewWillAppear:(BOOL)animated 78 | { 79 | [super viewWillAppear:animated]; 80 | } 81 | 82 | - (void)viewDidAppear:(BOOL)animated 83 | { 84 | [super viewDidAppear:animated]; 85 | } 86 | 87 | - (void)viewWillDisappear:(BOOL)animated 88 | { 89 | [super viewWillDisappear:animated]; 90 | } 91 | 92 | - (void)viewDidDisappear:(BOOL)animated 93 | { 94 | [super viewDidDisappear:animated]; 95 | } 96 | 97 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 98 | { 99 | return (interfaceOrientation == UIInterfaceOrientationPortrait); 100 | } 101 | 102 | #pragma mark - RSTwitterEngine Delegate Methods 103 | 104 | - (void)twitterEngine:(RSTwitterEngine *)engine needsToOpenURL:(NSURL *)url 105 | { 106 | WebViewController *vc = [[WebViewController alloc] initWithURL:url]; 107 | vc.delegate = self; 108 | 109 | [self presentModalViewController:[[UINavigationController alloc] initWithRootViewController:vc] animated:YES]; 110 | } 111 | 112 | - (void)twitterEngine:(RSTwitterEngine *)engine statusUpdate:(NSString *)message 113 | { 114 | self.statusLabel.text = message; 115 | } 116 | 117 | #pragma mark - WebViewController Delegate Methods 118 | 119 | - (void)dismissWebView 120 | { 121 | [self dismissModalViewControllerAnimated:YES]; 122 | if (self.twitterEngine) [self.twitterEngine cancelAuthentication]; 123 | } 124 | 125 | - (void)handleURL:(NSURL *)url 126 | { 127 | [self dismissModalViewControllerAnimated:YES]; 128 | 129 | if ([url.query hasPrefix:@"denied"]) { 130 | if (self.twitterEngine) [self.twitterEngine cancelAuthentication]; 131 | } else { 132 | if (self.twitterEngine) [self.twitterEngine resumeAuthenticationFlowWithURL:url]; 133 | } 134 | } 135 | 136 | #pragma mark - Custom Methods 137 | 138 | - (void)swipedRight:(UIGestureRecognizer *)recognizer 139 | { 140 | if (self.twitterEngine) [self.twitterEngine forgetStoredToken]; 141 | self.statusLabel.text = @"Not signed in."; 142 | } 143 | 144 | - (IBAction)sendTweet:(id)sender 145 | { 146 | if (self.twitterEngine) 147 | { 148 | self.navigationItem.rightBarButtonItem.enabled = NO; 149 | 150 | [self.twitterEngine sendTweet:self.textView.text withCompletionBlock:^(NSError *error) { 151 | if (error) { 152 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 153 | message:[error localizedDescription] 154 | delegate:nil 155 | cancelButtonTitle:@"Dismiss" 156 | otherButtonTitles:nil]; 157 | [alert show]; 158 | } else { 159 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"QuickTweet!" 160 | message:@"Tweet posted successfully!" 161 | delegate:nil 162 | cancelButtonTitle:@"Dismiss" 163 | otherButtonTitles:nil]; 164 | [alert show]; 165 | 166 | self.textView.text = @""; 167 | } 168 | 169 | self.navigationItem.rightBarButtonItem.enabled = YES; 170 | 171 | if (self.twitterEngine.isAuthenticated) { 172 | self.statusLabel.text = [NSString stringWithFormat:@"Signed in as @%@.", self.twitterEngine.screenName]; 173 | } else { 174 | self.statusLabel.text = @"Not signed in."; 175 | } 176 | }]; 177 | } 178 | } 179 | 180 | @end 181 | -------------------------------------------------------------------------------- /TwitterDemo/WebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.h 3 | // TwitterDemo 4 | // 5 | // Created by Rodrigo Sieiro on 12/11/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | @protocol WebViewControllerDelegate; 29 | 30 | @interface WebViewController : UIViewController 31 | 32 | @property (retain, nonatomic) NSURL *currentURL; 33 | 34 | @property (assign, nonatomic) id delegate; 35 | @property (unsafe_unretained, nonatomic) IBOutlet UIWebView *webView; 36 | @property (unsafe_unretained, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; 37 | 38 | - (id)initWithURL:(NSURL *)url; 39 | - (IBAction)cancelAction:(id)sender; 40 | 41 | @end 42 | 43 | @protocol WebViewControllerDelegate 44 | 45 | - (void)dismissWebView; 46 | - (void)handleURL:(NSURL *)url; 47 | 48 | @end -------------------------------------------------------------------------------- /TwitterDemo/WebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.m 3 | // TwitterDemo 4 | // 5 | // Created by Rodrigo Sieiro on 12/11/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "WebViewController.h" 27 | #import "AppDelegate.h" 28 | 29 | @implementation WebViewController 30 | 31 | #pragma mark - Initialization 32 | 33 | - (id)initWithURL:(NSURL *)url 34 | { 35 | self = [self initWithNibName:@"WebViewController" bundle:nil]; 36 | 37 | if (self) { 38 | self.currentURL = url; 39 | self.delegate = nil; 40 | } 41 | 42 | return self; 43 | } 44 | 45 | #pragma mark - View Lifecycle 46 | 47 | - (void)viewDidLoad 48 | { 49 | [super viewDidLoad]; 50 | 51 | self.title = @"Twitter"; 52 | self.webView.scalesPageToFit = YES; 53 | [self.webView loadRequest:[NSURLRequest requestWithURL:self.currentURL]]; 54 | } 55 | 56 | - (void)viewDidUnload 57 | { 58 | [self setWebView:nil]; 59 | [self setActivityIndicator:nil]; 60 | 61 | [super viewDidUnload]; 62 | } 63 | 64 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 65 | { 66 | return (interfaceOrientation == UIInterfaceOrientationPortrait); 67 | } 68 | 69 | #pragma mark - UIWebView Delegate Methods 70 | 71 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 72 | { 73 | if ([request.URL.scheme isEqualToString:@"rstwitterengine"] && (self.delegate)) { 74 | if (self.activityIndicator) [self.activityIndicator stopAnimating]; 75 | [self.delegate handleURL:request.URL]; 76 | return NO; 77 | } else { 78 | return YES; 79 | } 80 | } 81 | 82 | - (void)webViewDidStartLoad:(UIWebView *)webView 83 | { 84 | if (self.activityIndicator) [self.activityIndicator startAnimating]; 85 | } 86 | 87 | - (void)webViewDidFinishLoad:(UIWebView *)webView 88 | { 89 | if (self.activityIndicator) [self.activityIndicator stopAnimating]; 90 | } 91 | 92 | - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error 93 | { 94 | if (self.activityIndicator) [self.activityIndicator stopAnimating]; 95 | 96 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 97 | message:[error localizedDescription] 98 | delegate:nil 99 | cancelButtonTitle:@"Dismiss" 100 | otherButtonTitles:nil]; 101 | [alert show]; 102 | } 103 | 104 | #pragma mark - Custom Methods 105 | 106 | - (IBAction)cancelAction:(id)sender 107 | { 108 | if (self.delegate) [self.delegate dismissWebView]; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /TwitterDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /TwitterDemo/en.lproj/ViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /TwitterDemo/en.lproj/WebViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /TwitterDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TwitterDemo 4 | // 5 | // Created by Rodrigo Sieiro on 12/13/11. 6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | #import "AppDelegate.h" 29 | 30 | int main(int argc, char *argv[]) 31 | { 32 | @autoreleasepool { 33 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /logo_cube_114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsieiro/RSOAuthEngine/ba43ddb52601df819b37e0e1a7257ca9d9ff815a/logo_cube_114.png -------------------------------------------------------------------------------- /logo_cube_57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsieiro/RSOAuthEngine/ba43ddb52601df819b37e0e1a7257ca9d9ff815a/logo_cube_57.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsieiro/RSOAuthEngine/ba43ddb52601df819b37e0e1a7257ca9d9ff815a/screenshot.png --------------------------------------------------------------------------------