-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathSSYOAuthTalker.m
664 lines (593 loc) · 21.6 KB
/
SSYOAuthTalker.m
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
#import "SSYOAuthTalker.h"
#import "SSYUuid.h"
#import "SSYKeychain.h"
#import "NSURL+OAuth.h"
#import "NSString+OAuth.h"
#import "SSYSynchronousHttp.h"
#import "NSError+MyDomain.h"
#import "NSString+Data.h"
#import "NSString+URIQuery.h"
#import "NSString+LocalizeSSY.h"
#import "NSObject+MoreDescriptions.h"
#import "NSBundle+MainApp.h"
NSString* const constNoteGotOAuthInfo = @"gotOAuthInfo" ;
NSString* const constPathOAuth = @"OAuth" ;
NSString* const SSYOAuthTalkerErrorDomain = @"SSYOAuthTalkerErrorDomain" ;
// Keys sent to and/or received from Provider
NSString* const constKeyOAuthCallback = @"oauth_callback" ;
NSString* const constKeyOAuthConsumerKey = @"oauth_consumer_key" ;
NSString* const constKeyOAuthNonce = @"oauth_nonce" ;
NSString* const constKeyOAuthSignature = @"oauth_signature" ;
NSString* const constKeyOAuthSignatureMethod = @"oauth_signature_method" ;
NSString* const constKeyOAuthTimestamp = @"oauth_timestamp" ;
NSString* const constKeyOAuthVersion = @"oauth_version" ;
NSString* const constKeyOAuthRequestUrl = @"xoauth_request_auth_url" ;
NSString* const constKeyOAuthToken = @"oauth_token" ;
NSString* const constKeyOAuthTokenSecret = @"oauth_token_secret" ;
NSString* const constKeyOAuthVerifier = @"oauth_verifier" ;
NSString* const constKeyOAuthSessionHandle = @"oauth_session_handle" ;
NSString* const constKeyXOAuthYahooGuid = @"xoauth_yahoo_guid" ;
NSString* const constKeyOAuthAuthorizationHeaderKey = @"Authorization" ; // http://tools.ietf.org/html/draft-hammer-oauth-10#section-3.5.1
NSString* const constValueOAuthSignatureMethod = @"HMAC-SHA1" ;
NSString* const constValueOAuthVersion = @"1.0" ;
@implementation SSYOAuthTalker
@synthesize consumerKey = m_consumerKey ;
@synthesize consumerSecret = m_consumerSecret ;
@synthesize oAuthToken = m_oAuthToken ;
@synthesize oAuthTokenSecret = m_oAuthTokenSecret ;
@synthesize oAuthVerifier = m_oAuthVerifier ;
@synthesize oAuthSessionHandle = m_oAuthSessionHandle ;
@synthesize guid = m_guid ;
@synthesize oAuthRealm = m_oAuthRealm ;
@synthesize requestAccessUrl = m_requestAccessUrl ;
@synthesize apiUrl = m_apiUrl ;
@synthesize timeout = m_timeout ;
@synthesize gotAccessInvocation = m_gotAccessInvocation ;
@synthesize accounter = m_accounter ;
+ (NSString*)keychainServiceNameForAccounter:(NSObject <SSYOAuthAccounter> *)accounter {
NSString* answer = [[NSBundle mainAppBundle] bundleIdentifier] ;
if ([accounter respondsToSelector:@selector(serviceName)]) {
answer = [answer stringByAppendingFormat:
@".%@",
[accounter serviceName]] ;
}
return answer ;
}
- (NSString*)keychainServiceName {
return [SSYOAuthTalker keychainServiceNameForAccounter:[self accounter]] ;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self] ;
[m_consumerKey release] ;
[m_consumerSecret release] ;
[m_oAuthToken release] ;
[m_oAuthTokenSecret release] ;
[m_oAuthVerifier release] ;
[m_oAuthSessionHandle release] ;
[m_guid release] ;
[m_oAuthRealm release] ;
[m_requestAccessUrl release] ;
[m_apiUrl release] ;
[m_gotAccessInvocation release] ;
[super dealloc] ;
}
- (id)initWithAccounter:(NSObject <SSYOAuthAccounter> *)accounter {
self = [super init] ;
if (self) {
[self setAccounter:accounter] ;
[self setTimeout:17.0] ;
}
return self ;
}
/*!
@brief Method which sends all messages to the provider's server
and synchronously returns (by reference) the response info.
@details This method generates and adds the Six Basic Parameters
into the query for you. The Six Basic Parameters are the consumer key,
nonce, signature method, timestamp, OAuth version and, if not nil, the OAuth token.
It also creates and adds in the signature base string and HMAC-SHA1 signature.
@param url The URL to which the message should be sent. Example:
@"https://api.login.yahoo.com/oauth/v2/get_request_token"
@param moreParms Auth parameters in addition to the Six
Basic parameters. May be nil if no more parameters. Examples:
* For the initial authorization request, should contain only constKeyOAuthCallback.
* For the first access request, should only constKeyOAuthVerifier.
* For refresh requests, should contain only constKeyOAuthSessionHandle.
* For API requests, none. Pass nil.
@param apiParms Parameters which will be passed to the API
by appending to the URL a query string generated by encoding the
keys and values in this dictionary. May be nil, and normally
is when making authorization or access requests.
@param wrap If YES, wraps the authorization parameters into
a comma-separated "Authorization" header beginning with the Realm
if any, and makes the request with HTTP method GET. If NO, sends
the authorization parameters in the body of the request and makes
the request with HTTP method POST. Short answer: Pass NO for
authorization and access requests, and YES for API requests.
Probably this could be explained in more elegant terms if one
would take the time to read the OAuth standard document.
@param data_p Pointer to a data object which, upon return, will
point to the content of the server's response. If you do not need
this, as for example when making authorization or access requests
and reading the response from info_p, you may pass NULL.
@param info_p Pointer to a dictionary which, upon return, will
contain the decoded keys and values sent in the data from the server's
response, but only if this data is in fact a URL-encoded query string
of the form
name1=value1&name2=value2, etc.
If the received content is not of this form, *info_p is undefined.
If the expected receive is not of this form, or if you don't need
it, you may pass NULL. This is usually the case when making
API requests, since the received content will be XML.
@param error_p Pointer which will, upon return, if an error
occurred and said pointer is not NULL, point to an NSError
describing said error.
@result YES if the method executed without error, otherwise NO.
*/
+ (BOOL)requestToUrl:(NSString*)url
token:(NSString*)token
tokenSecret:(NSString*)tokenSecret
consumerKey:(NSString*)consumerKey
consumerSecret:(NSString*)consumerSecret
realm:(NSString*)realm
moreAuthParms:(NSDictionary*)moreAuthParms
apiParms:(NSDictionary*)apiParms
wrap:(BOOL)wrap
timeout:(NSTimeInterval)timeout
returnData:(NSData**)data_p
returnInfo:(NSDictionary**)info_p
error_p:(NSError**)error_p {
BOOL ok = YES ;
NSError* error = nil ;
NSData* data = nil ;
if (!url) {
error = SSYMakeError(456973, @"No url for OAuth") ;
goto end ;
}
NSString* nonce = [SSYUuid compactUuid] ;
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970] ;
NSString* timestamp = [NSString stringWithFormat:@"%0.0f", timeInterval] ;
// Secrets must be urlencoded and then concatenated with '&'
NSMutableDictionary* parms = [NSMutableDictionary dictionaryWithObjectsAndKeys:
consumerKey, constKeyOAuthConsumerKey,
nonce, constKeyOAuthNonce,
constValueOAuthSignatureMethod, constKeyOAuthSignatureMethod,
timestamp, constKeyOAuthTimestamp,
constValueOAuthVersion, constKeyOAuthVersion,
nil] ;
if (token) {
[parms setObject:token
forKey:constKeyOAuthToken] ;
}
if (moreAuthParms) {
[parms addEntriesFromDictionary:moreAuthParms] ;
}
if (apiParms) {
[parms addEntriesFromDictionary:apiParms] ;
}
NSString* httpMethod = wrap ? @"GET" : @"POST" ;
// Create the signature base string.
NSURL* uurl = [NSURL URLWithString:url] ;
NSString* queryBeforeSigning = [NSString stringOAuthWithQueryDictionary:parms] ;
NSString* signatureBaseString = [NSString stringWithFormat:
@"%@&%@&%@",
[[httpMethod uppercaseString] stringByPercentEscapeEncodingForOAuth],
[[uurl normalizedUrlForOAuth] stringByPercentEscapeEncodingForOAuth],
// Note: The query string is already encoded. The following
// line will make it doubly encoded. This is per standard doc:
// http://tools.ietf.org/html/draft-hammer-oauth-10#section-3.4.1.1
[queryBeforeSigning stringByPercentEscapeEncodingForOAuth]] ;
if (!tokenSecret) {
// Must be initial request for authorization info.
// OAuth spec says to just use consumer secret followed by a hanging ampersand
tokenSecret = @"" ;
}
NSString* compositeSecret = [NSString stringWithFormat:@"%@&%@",
[consumerSecret stringByPercentEscapeEncodingForOAuth],
[tokenSecret stringByPercentEscapeEncodingForOAuth]] ;
NSString* signature = [signatureBaseString HMACSHA1SignatureWithSecret:compositeSecret] ;
[parms setObject:signature
forKey:constKeyOAuthSignature] ;
NSDictionary* headers ;
NSString* bodyString ;
if (wrap) {
// Construct a HTTP headers dictionary consisting of one value,
// an "Authorization".
[parms removeObjectsForKeys:[apiParms allKeys]] ;
NSMutableString* authParmValue = [[NSMutableString alloc] init] ;
if (realm) {
[authParmValue appendFormat:
@"OAuth realm=\"%@\"",
realm] ;
}
for (NSString* key in [parms allKeys]) {
[authParmValue appendFormat:
@",%@=\"%@\"",
key,
[[parms objectForKey:key] stringByPercentEscapeEncodingForOAuth]] ;
}
headers = [NSDictionary dictionaryWithObject:authParmValue
forKey:constKeyOAuthAuthorizationHeaderKey] ;
[authParmValue release] ;
bodyString = nil ;
}
else {
headers = nil ;
bodyString = [NSString stringOAuthWithQueryDictionary:parms] ;
}
NSMutableString* apiQueryString ;
NSString* fullUrl = [uurl absoluteString] ;
if ([apiParms count] > 0) {
apiQueryString = [NSMutableString string] ;
BOOL firstPair = YES ;
// I'm not sure if this is part of the OAuth standard, or just Delicious, but
// if the key/value pairs in the API query string are not sorted alphabetically,
// I get a "signature_invalid" error.
for (NSString* key in [[apiParms allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
NSString* delimiter ;
if (firstPair) {
delimiter = @"?" ;
firstPair = NO ;
}
else {
delimiter = @"&" ;
}
[apiQueryString appendFormat:
@"%@%@=%@",
delimiter,
key,
[[apiParms objectForKey:key] stringByPercentEscapeEncodingForOAuth]] ;
}
NSMutableString* fullUrlMutant = [fullUrl mutableCopy] ;
[fullUrlMutant appendString:apiQueryString] ;
fullUrl = fullUrlMutant ;
[fullUrl autorelease] ;
}
NSHTTPURLResponse* response ;
ok = [SSYSynchronousHttp SSYSynchronousHttpUrl:fullUrl
httpMethod:httpMethod
headers:headers
bodyString:bodyString
username:nil
password:nil
timeout:timeout
userAgent:nil
response_p:&response
receiveData_p:&data
error_p:&error] ;
#if 0
#warning Logging SSYOAuthTalker HTTP Response & Rx Data
NSLog(@"Response Headers:\n%@", [response allHeaderFields]) ;
NSLog(@"Response Data (UTF8 decoded):\n%@", [NSString stringWithDataUTF8:data]) ;
#endif
end:
if (ok) {
if (data_p) {
*data_p = data ;
}
if (info_p) {
NSString* string = [NSString stringWithDataUTF8:data] ;
*info_p = [string queryDictionary] ;
}
}
else {
if (error_p) {
*error_p = error ;
}
}
return ok ;
}
- (BOOL)getAuthorizationInfoFromRequestUrl:(NSString*)requestUrl
authorizationInfo_p:(NSDictionary**)authorizationInfo_p
error_p:(NSError**)error_p {
NSDictionary* info = nil ;
NSError* error = nil ;
BOOL ok;
#if 0
#warning Using private url scheme callback no longer supported by Yahoo! except for Pukka which must be special
NSString* callback = [NSString stringWithFormat:
@"%@://%@",
[[BkmxBasis sharedBasis] appUrlScheme],
constPathOAuth] ;
#else
NSString* callback = @"oob" ;
#endif
NSDictionary* moreParms = [NSDictionary dictionaryWithObjectsAndKeys:
callback, constKeyOAuthCallback,
nil] ;
// Get Authorization Info
ok= [[self class] requestToUrl:requestUrl
token:nil
tokenSecret:nil
consumerKey:[self consumerKey]
consumerSecret:[self consumerSecret]
realm:nil
moreAuthParms:moreParms
apiParms:nil
wrap:NO
timeout:[self timeout]
returnData:NULL
returnInfo:&info
error_p:&error] ;
if (ok) {
*authorizationInfo_p = info ;
}
else if (error_p) {
*error_p = error ;
}
return ok ;
}
/*!
@brief Method which gets an access token, either initially or
refresh.
*/
- (BOOL)getAccessError_p:(NSError**)error_p {
NSDictionary* accessInfo = nil ;
NSError* error = nil ;
BOOL ok = YES ;
NSMutableDictionary* moreParms = [NSMutableDictionary dictionary] ;
NSString* verifier = [self oAuthVerifier] ;
if (verifier) {
// Must be first attempt to get access, not a refresh
[moreParms setObject:verifier
forKey:constKeyOAuthVerifier] ;
}
else {
NSString* sessionHandle = [self oAuthSessionHandle] ;
// Must be a refresh
if (sessionHandle) {
[moreParms setObject:sessionHandle
forKey:constKeyOAuthSessionHandle] ;
}
else {
ok = NO ;
error = [NSError errorWithDomain:SSYOAuthTalkerErrorDomain
code:SSYOAuthTalkerNoVerifierNoSessionHandleErrorCode
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
@"Insufficient values to request access", NSLocalizedDescriptionKey,
nil]] ;
}
}
if (!ok) {
goto end ;
}
ok= [[self class] requestToUrl:[self requestAccessUrl]
token:[self oAuthToken]
tokenSecret:[self oAuthTokenSecret]
consumerKey:[self consumerKey]
consumerSecret:[self consumerSecret]
realm:nil
moreAuthParms:moreParms
apiParms:nil
wrap:NO
timeout:[self timeout]
returnData:NULL
returnInfo:&accessInfo
error_p:&error] ;
if (!ok) {
goto end ;
}
[self setOAuthSessionHandle:[accessInfo objectForKey:constKeyOAuthSessionHandle]] ;
[self setOAuthToken:[accessInfo objectForKey:constKeyOAuthToken]] ;
[self setOAuthTokenSecret:[accessInfo objectForKey:constKeyOAuthTokenSecret]] ;
[self setGuid:[accessInfo objectForKey:constKeyXOAuthYahooGuid]] ;
end:;
if (error_p) {
*error_p = error ;
}
return ok ;
}
- (BOOL)processOAuthVerifier:(NSString*)verifier {
[self setOAuthVerifier:verifier] ;
NSError* error = nil ;
BOOL ok ;
ok = [self getAccessError_p:&error] ;
// The verifier can only be used once
[self setOAuthVerifier:nil] ;
[[self gotAccessInvocation] invoke] ;
[self setGotAccessInvocation:nil] ;
return ok ;
}
/*!
@brief Notification handler which should be invoked when the
gotDeliciousOAuthInfo notification has been received from your app's
URL handler.
@details In your URL handler, when you send the notification, set
the notification object to the query string that was received
from the server. This method will extract the value of the oauth_verifier key
from that string.
*/
- (void)processOAuthInfo:(NSNotification*)note {
NSString* query = [note object] ;
NSDictionary* info = [query queryDictionary] ;
// Note: moreInfo also contains the oauth_token, but we already got that in the
// response during getUnauthorizedRequestInfo:: and have stored it in an ivar.
NSString* verifier = [info objectForKey:constKeyOAuthVerifier] ;
[self processOAuthVerifier:verifier] ;
}
- (BOOL)getPasswordFromKeychain {
BOOL ok = NO ;
NSString* password = [SSYKeychain passwordForServost:[self keychainServiceName]
trySubhosts:nil
account:[[self accounter] accountName]
clase:(NSString*)kSecClassGenericPassword
error_p:NULL] ;
if (password) {
// Since the password is accessible i.e. corruptible by the user
// and other apps, we try to handle anything without crashing.
NSScanner* scanner = [[NSScanner alloc] initWithString:password] ;
NSString* token = @"No Token" ;
[scanner scanUpToString:@"\n"
intoString:&token] ;
NSInteger nextLocation ;
nextLocation = [scanner scanLocation] + 1 ;
if (nextLocation < [password length]) {
[scanner setScanLocation:nextLocation] ;
}
NSString* tokenSecret = @"No Token Secret" ;
[scanner scanUpToString:@"\n"
intoString:&tokenSecret] ;
nextLocation = [scanner scanLocation] + 1 ;
if (nextLocation < [password length]) {
[scanner setScanLocation:nextLocation] ;
}
NSString* guid = @"No GUID" ;
[scanner scanUpToString:@"\n"
intoString:&guid] ;
NSInteger scanLocation = [scanner scanLocation] ;
[scanner release] ;
NSString* sessionHandle = @"No Session Handle" ;
if ([password length] > scanLocation + 1) {
// Note: Although we're only requiring 0 or 1,
// sessionHandle should actually be 40 characters.
sessionHandle = [password substringFromIndex:(scanLocation + 1)] ;
}
if (token && tokenSecret && guid && sessionHandle) {
[self setOAuthToken:token] ;
[self setOAuthTokenSecret:tokenSecret] ;
[self setGuid:guid] ;
[self setOAuthSessionHandle:sessionHandle] ;
ok = YES ;
}
}
return ok ;
}
- (void)setPasswordToKeychain {
// Pukka maps to Keychain as accountName=token and password=tokenSecret.
// I don't like that. We also need the session handle, for refreshing the token,
// and the GUID, for making API requests.
// So I 4-plex the token, token secret, GUID and session handle into the password,
// using a newline as a delimiter, and use the profile name as the account
// name.
NSString* password = [NSString stringWithFormat:
@"%@\n%@\n%@\n%@",
[self oAuthToken],
[self oAuthTokenSecret],
[self guid],
[self oAuthSessionHandle]] ;
// Even though this password goes over the internet, a generic password type
// is more appropriate than an internet password type because
// (a) It does not have a host name associated with it
// (b) Due to registration of BookMacster as a "consumer" with Yahoo!,
// it will not work with any other app.
// (c) Pukka does it as a generic password.
[SSYKeychain setPassword:password
forServost:[self keychainServiceName]
account:[[self accounter] accountName]
clase:(NSString*)kSecClassGenericPassword
error_p:NULL] ;
}
- (BOOL)requestCommand:(NSString*)command
parms:(NSDictionary*)parms
timeout:(NSTimeInterval)timeout
returnData_p:(NSData**)returnData_p
error_p:(NSError**)error_p {
NSString* apiUrl = [self apiUrl] ;
NSString* url = [apiUrl stringByAppendingString:command] ;
NSInteger state = 0 ;
BOOL ok ;
NSError* error = nil ;
BOOL done = NO ;
while(!done) {
ok = [[self class] requestToUrl:url
token:[self oAuthToken]
tokenSecret:[self oAuthTokenSecret]
consumerKey:[self consumerKey]
consumerSecret:[self consumerSecret]
realm:[self oAuthRealm]
moreAuthParms:nil
apiParms:parms
wrap:YES
timeout:timeout
returnData:returnData_p
returnInfo:NULL
error_p:&error] ;
if (ok) {
break ;
}
state += 1 ;
switch (state) {
case 1:
ok = [self getPasswordFromKeychain] ;
if (!ok) {
done = YES ;
NSDictionary* userInfo = [NSDictionary dictionaryWithObject:@"Credential not found in Keychain"
forKey:NSLocalizedDescriptionKey] ;
error = [NSError errorWithDomain:SSYOAuthTalkerErrorDomain
code:SSYOAuthTalkerCredentialNotFoundErrorCode
userInfo:userInfo] ;
}
break ;
case 2:
// Maybe the password (token, tokenSecret) needs a refresh
// They are only good for 60 minutes.
ok = [self getAccessError_p:&error] ;
if (!ok) {
done = YES ;
}
break ;
case 3:
// Refreshing access did not work. Possibly the user
// inadvertantly de-authorized this app with the service.
done = YES ;
break ;
}
}
if (ok) {
if (state > 1) {
// We got a new password
[self setPasswordToKeychain] ;
}
}
else if (error_p) {
// Whether we have an error already or not, we're going to add more userInfo
NSMutableDictionary* userInfo = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
url, @"Full URL",
[NSNumber numberWithInteger:state], @"State",
nil] ;
NSInteger code ;
if (error) {
code = [error code] ;
[userInfo addEntriesFromDictionary:[error userInfo]] ;
}
else {
code = SSYOAuthTalkerRequestFailedErrorCode ;
NSString* description = [NSString stringWithFormat:
@"%@:\n %@",
[NSString localize:@"connectionFailed"],
[self apiUrl]] ;
[userInfo setObject:description
forKey:NSLocalizedDescriptionKey] ;
}
*error_p = [NSError errorWithDomain:SSYOAuthTalkerErrorDomain
code:code
userInfo:userInfo] ;
[userInfo release] ;
}
return ok ;
}
+ (BOOL)testRequest {
NSError* error = nil ;
NSData* returnData = nil ;
BOOL ok = [self requestToUrl:@"http://term.ie/oauth/example/request_token.php"
token:nil
tokenSecret:nil
consumerKey:@"key"
consumerSecret:@"secret"
realm:nil
moreAuthParms:nil
apiParms:nil
wrap:NO
timeout:18.5
returnData:&returnData
returnInfo:NULL
error_p:&error] ;
printf("This test sends a request to http://term.ie/oauth/example/request_token.php\n") ;
printf("For more information: http://term.ie/oauth/example/\n") ;
printf("Expected Data: oauth_token=requestkey&oauth_token_secret=requestsecret\n") ;
printf("Received Data: %s\n", [[NSString stringWithDataUTF8:returnData] UTF8String]) ;
printf("Error: %s\n", [[error longDescription] UTF8String]) ;
printf("Test Result: %s\n", ok ? "Passed!" : "Failed.") ;
return ok ;
}
@end