Skip to content
Newer
Older
100644 782 lines (616 sloc) 24.3 KB
2e48864 First public release
Ben Copsey authored
1 //
2 // ASIHTTPRequest.m
3 //
4 // Created by Ben Copsey on 04/10/2007.
5 // Copyright 2007-2008 All-Seeing Interactive. All rights reserved.
6 //
7 // Portions are based on the ImageClient example from Apple:
8 // See: http://developer.apple.com/samplecode/ImageClient/listing37.html
9
10 #import "ASIHTTPRequest.h"
11
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
12 static NSString *NetworkRequestErrorDomain = @"com.Your-Company.Your-Product.NetworkError.";
2e48864 First public release
Ben Copsey authored
13
14 static const CFOptionFlags kNetworkEvents = kCFStreamEventOpenCompleted |
15 kCFStreamEventHasBytesAvailable |
16 kCFStreamEventEndEncountered |
17 kCFStreamEventErrorOccurred;
18
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
19 static CFHTTPAuthenticationRef sessionAuthentication = NULL;
20 static NSMutableDictionary *sessionCredentials = nil;
2e48864 First public release
Ben Copsey authored
21
22
23 static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) {
24 [((ASIHTTPRequest*)clientCallBackInfo) handleNetworkEvent: type];
25 }
26
27
28 @implementation ASIHTTPRequest
29
753b0fb Convert getters / setters to obj-c 2.0 properties
Ben Copsey authored
30
31
2e48864 First public release
Ben Copsey authored
32 #pragma mark init / dealloc
33
34 - (id)initWithURL:(NSURL *)newURL
35 {
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
36 [super init];
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
37 lastBytesSent = 0;
2e48864 First public release
Ben Copsey authored
38 postData = nil;
39 fileData = nil;
40 username = nil;
41 password = nil;
42 requestHeaders = nil;
43 authenticationRealm = nil;
44 outputStream = nil;
fe503b0 Added basic unit tests
Ben Copsey authored
45 requestAuthentication = NULL;
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
46 //credentials = NULL;
2e48864 First public release
Ben Copsey authored
47 request = NULL;
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
48 responseHeaders = nil;
49 [self setUseKeychainPersistance:YES];
50 [self setUseSessionPersistance:YES];
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
51 didFinishSelector = @selector(requestFinished:);
52 didFailSelector = @selector(requestFailed:);
753b0fb Convert getters / setters to obj-c 2.0 properties
Ben Copsey authored
53 delegate = nil;
fe503b0 Added basic unit tests
Ben Copsey authored
54 url = [newURL retain];
55 return self;
2e48864 First public release
Ben Copsey authored
56 }
57
58 - (void)dealloc
59 {
fe503b0 Added basic unit tests
Ben Copsey authored
60 if (requestAuthentication) {
61 CFRelease(requestAuthentication);
2e48864 First public release
Ben Copsey authored
62 }
63 if (request) {
64 CFRelease(request);
65 }
66 [self cancelLoad];
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
67 [requestCredentials release];
2e48864 First public release
Ben Copsey authored
68 [error release];
69 [postData release];
70 [fileData release];
71 [requestHeaders release];
72 [downloadDestinationPath release];
73 [outputStream release];
74 [username release];
75 [password release];
76 [authenticationRealm release];
77 [url release];
78 [authenticationLock release];
79 [super dealloc];
80 }
81
82
83 #pragma mark setup request
84
85 - (void)addRequestHeader:(NSString *)header value:(NSString *)value
86 {
87 if (!requestHeaders) {
88 requestHeaders = [[NSMutableDictionary alloc] init];
89 }
90 [requestHeaders setObject:value forKey:header];
91 }
92
93
94 - (void)setPostValue:(id)value forKey:(NSString *)key
95 {
96 if (!postData) {
97 postData = [[NSMutableDictionary alloc] init];
98 }
99 [postData setValue:value forKey:key];
100 }
101
102 - (void)setFile:(NSString *)filePath forKey:(NSString *)key
103 {
104 if (!fileData) {
105 fileData = [[NSMutableDictionary alloc] init];
106 }
107 [fileData setValue:filePath forKey:key];
108 }
109
110 - (void)setUsername:(NSString *)newUsername andPassword:(NSString *)newPassword
111 {
112 [username release];
113 username = [newUsername retain];
114 [password release];
115 password = [newPassword retain];
116 }
117
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
118
119
120 #pragma mark get information about this request
121
2e48864 First public release
Ben Copsey authored
122 - (BOOL)isFinished
123 {
124 return complete;
125 }
126
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
127 - (double)totalBytesRead
128 {
129 return totalBytesRead;
130 }
131
132 // Call this method to get the recieved data as an NSString. Don't use for Binary data!
133 - (NSString *)dataString
134 {
135 if (!receivedData) {
136 return nil;
137 }
753b0fb Convert getters / setters to obj-c 2.0 properties
Ben Copsey authored
138 return [[[NSString alloc] initWithBytes:[(NSData *)receivedData bytes] length:[(NSData *)receivedData length] encoding:NSUTF8StringEncoding] autorelease];
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
139 }
140
2e48864 First public release
Ben Copsey authored
141
142 #pragma mark request logic
143
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
144 // Create the request
2e48864 First public release
Ben Copsey authored
145 - (void)main
146 {
147 complete = NO;
148
149 // We'll make a post request only if the user specified post data
150 NSString *method = @"GET";
151 if ([postData count] > 0 || [fileData count] > 0) {
152 method = @"POST";
153 }
154
155 // Create a new HTTP request.
156 request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)method, (CFURLRef)url, kCFHTTPVersion1_1);
157 if (!request) {
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
158 [self failWithProblem:[NSString stringWithFormat:@"Unable to create request for: %@",url]];
2e48864 First public release
Ben Copsey authored
159 return;
160 }
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
161
162 //If we've already talked to this server and have valid credentials, let's apply them to the request
163 if (useSessionPersistance && sessionCredentials && sessionAuthentication) {
164 if (!CFHTTPMessageApplyCredentialDictionary(request, sessionAuthentication, (CFMutableDictionaryRef)sessionCredentials, NULL)) {
fe503b0 Added basic unit tests
Ben Copsey authored
165 [ASIHTTPRequest setSessionAuthentication:NULL];
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
166 [ASIHTTPRequest setSessionCredentials:nil];
167 }
2e48864 First public release
Ben Copsey authored
168 }
169
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
170 //Set your own boundary string only if really obsessive. We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does.
2e48864 First public release
Ben Copsey authored
171 NSString *stringBoundary = @"0xKhTmLbOuNdArY";
172
173 //Add custom headers
174 NSString *header;
175 for (header in requestHeaders) {
176 CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[requestHeaders objectForKey:header]);
177 }
178 CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)@"Content-Type", (CFStringRef)[NSString stringWithFormat:@"multipart/form-data; boundary=%@",stringBoundary]);
179
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
180
181 if ([postData count] > 0 || [fileData count] > 0) {
2e48864 First public release
Ben Copsey authored
182
183 NSMutableData *postBody = [NSMutableData data];
184 [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
185
186 //Adds post data
187 NSData *endItemBoundary = [[NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding];
188 NSEnumerator *e = [postData keyEnumerator];
189 NSString *key;
190 while (key = [e nextObject]) {
191 [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key] dataUsingEncoding:NSUTF8StringEncoding]];
192 [postBody appendData:[[postData objectForKey:key] dataUsingEncoding:NSUTF8StringEncoding]];
193 [postBody appendData:endItemBoundary];
194 }
195
196 //Adds files to upload
197 NSData *contentTypeHeader = [[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding];
198 e = [fileData keyEnumerator];
199 while (key = [e nextObject]) {
200 NSString *filePath = [fileData objectForKey:key];
201 [postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n",key,[filePath lastPathComponent]] dataUsingEncoding:NSUTF8StringEncoding]];
202 [postBody appendData:contentTypeHeader];
203 [postBody appendData:[NSData dataWithContentsOfMappedFile:filePath]];
204 [postBody appendData:endItemBoundary];
205 }
206
207 [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
208
209 // Set the body.
210 CFHTTPMessageSetBody(request, (CFDataRef)postBody);
211
212 postLength = [postBody length];
213 }
214
215 [self loadRequest];
216
217 }
218
219
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
220 // Start the request
2e48864 First public release
Ben Copsey authored
221 - (void)loadRequest
222 {
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
223
2e48864 First public release
Ben Copsey authored
224 [authenticationLock release];
225 authenticationLock = [[NSConditionLock alloc] initWithCondition:1];
226
227 complete = NO;
228 totalBytesRead = 0;
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
229 lastBytesRead = 0;
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
230
231 //If we're retrying a request after an authentication failure, let's remove any progress we made
232 if (lastBytesSent > 0 && uploadProgressDelegate) {
233 [uploadProgressDelegate setDoubleValue:[uploadProgressDelegate doubleValue]-lastBytesSent];
234 [uploadProgressDelegate setMaxValue:[uploadProgressDelegate maxValue]-lastBytesSent];
235 }
236
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
237 lastBytesSent = 0;
2e48864 First public release
Ben Copsey authored
238 contentLength = 0;
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
239 [self setResponseHeaders:nil];
2e48864 First public release
Ben Copsey authored
240 receivedData = CFDataCreateMutable(NULL, 0);
241
242 // Create the stream for the request.
243 readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,readStream);
244 if (!readStream) {
245 [self failWithProblem:@"Unable to create read stream"];
246 return;
247 }
248
249 // Set the client
250 CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
251 if (!CFReadStreamSetClient(readStream, kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
252 CFRelease(readStream);
253 readStream = NULL;
254 [self failWithProblem:@"Unable to setup read stream"];
255 return;
256 }
257
258 // Schedule the stream
259 CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
260
261 // Start the HTTP connection
262 if (!CFReadStreamOpen(readStream)) {
263 CFReadStreamSetClient(readStream, 0, NULL, NULL);
264 CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
265 CFRelease(readStream);
266 readStream = NULL;
267 [self failWithProblem:@"Unable to start http connection"];
268 return;
269 }
270
271
272 if (uploadProgressDelegate) {
273 [self performSelectorOnMainThread:@selector(resetUploadProgress:) withObject:[NSNumber numberWithDouble:postLength] waitUntilDone:YES];
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
274 }
2e48864 First public release
Ben Copsey authored
275
276
277 // Wait for the request to finish
278 NSDate* endDate = [NSDate distantFuture];
279 while (!complete) {
280
281 // See if our NSOperationQueue told us to cancel
282 if ([self isCancelled]) {
283 [self failWithProblem:@"The request was cancelled"];
284 [self cancelLoad];
285 complete = YES;
286 break;
287 }
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
288 [self updateProgressIndicators];
2e48864 First public release
Ben Copsey authored
289 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:endDate];
290 }
291 }
292
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
293 // Cancel loading and clean up
2e48864 First public release
Ben Copsey authored
294 - (void)cancelLoad
295 {
296 if (readStream) {
297 CFReadStreamClose(readStream);
298 CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
299 CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
300 CFRelease(readStream);
301 readStream = NULL;
302 }
303
304 if (receivedData) {
305 CFRelease(receivedData);
306 receivedData = NULL;
307
308 //If we were downloading to a file, let's remove it
309 } else if (downloadDestinationPath) {
310 [outputStream close];
311 [[NSFileManager defaultManager] removeFileAtPath:downloadDestinationPath handler:nil];
312 }
313
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
314 [self setResponseHeaders:nil];
2e48864 First public release
Ben Copsey authored
315 }
316
317
318
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
319 #pragma mark upload/download progress
2e48864 First public release
Ben Copsey authored
320
321
322 - (void)updateProgressIndicators
323 {
324 [self updateUploadProgress];
325 [self updateDownloadProgress];
326
327 }
328
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
329 // Rather than reset the value to 0, it simply adds the size of the upload to the max.
330 // This allows multiple requests to use the same progress indicator, but you'll need to remember to set the indicator's value to 0 before you start!
331 // Alternatively, change or overidde this method to set the progress to 0 if you're only ever tracking the progress of a single request at a time
2e48864 First public release
Ben Copsey authored
332 - (void)resetUploadProgress:(NSNumber *)max
333 {
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
334 [uploadProgressDelegate setMaxValue:[uploadProgressDelegate maxValue]+[max doubleValue]];
2e48864 First public release
Ben Copsey authored
335 }
336
337 - (void)updateUploadProgress
338 {
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
339 double byteCount = [[(NSNumber *)CFReadStreamCopyProperty (readStream, kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease] doubleValue];
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
340 if (uploadProgressDelegate) {
341 [uploadProgressDelegate incrementBy:byteCount-lastBytesSent];
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
342 }
343 lastBytesSent = byteCount;
2e48864 First public release
Ben Copsey authored
344 }
345
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
346
347 // Will only be called if we get a content-length header.
348 // Rather than reset the value to 0, it simply adds the size of the download to the max.
349 // This allows multiple requests to use the same progress indicator, but you'll need to remember to set the indicator's value to 0 before you start!
350 // Alternatively, change or overidde this method to set the progress to 0 if you're only ever tracking the progress of a single request at a time
351 - (void)resetDownloadProgress:(NSNumber *)max
352 {
353 [downloadProgressDelegate setMaxValue:[downloadProgressDelegate maxValue]+[max doubleValue]];
354 }
355
2e48864 First public release
Ben Copsey authored
356 - (void)updateDownloadProgress
357 {
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
358 //We won't update downlaod progress until we've examined the headers, since we might need to authenticate
359 if (downloadProgressDelegate && responseHeaders) {
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
360 [downloadProgressDelegate incrementBy:totalBytesRead-lastBytesRead];
361 lastBytesRead = totalBytesRead;
2e48864 First public release
Ben Copsey authored
362 }
363 }
364
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
365 #pragma mark handling request complete / failure
2e48864 First public release
Ben Copsey authored
366
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
367
368 // Subclasses can override this method to process the result in the same thread
369 // If not overidden, it will call the didFinishSelector on the delegate, if one has been setup
370 - (void)requestFinished
2e48864 First public release
Ben Copsey authored
371 {
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
372 if (didFinishSelector && ![self isCancelled] && [delegate respondsToSelector:didFinishSelector]) {
373 [delegate performSelectorOnMainThread:didFinishSelector withObject:self waitUntilDone:YES];
374 }
375 }
376
377
378
379 // Subclasses can override this method to perform error handling in the same thread
380 // If not overidden, it will call the didFailSelector on the delegate (by default requestFailed:)`
381 - (void)failWithProblem:(NSString *)problem
382 {
383 complete = YES;
384 if (!error) {
753b0fb Convert getters / setters to obj-c 2.0 properties
Ben Copsey authored
385 [self setError:[NSError errorWithDomain:NetworkRequestErrorDomain
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
386 code:1
387 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"An error occurred",@"Title",
753b0fb Convert getters / setters to obj-c 2.0 properties
Ben Copsey authored
388 problem,@"Description",nil]]];
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
389 NSLog(problem);
390
391 if (didFailSelector && ![self isCancelled] && [delegate respondsToSelector:didFailSelector]) {
392 [delegate performSelectorOnMainThread:didFailSelector withObject:self waitUntilDone:YES];
393 }
394 }
395 }
2e48864 First public release
Ben Copsey authored
396
397
398 #pragma mark http authentication
399
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
400 - (BOOL)readResponseHeadersReturningAuthenticationFailure
401 {
2e48864 First public release
Ben Copsey authored
402 BOOL isAuthenticationChallenge = NO;
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
403 CFHTTPMessageRef headers = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
404 if (CFHTTPMessageIsHeaderComplete(headers)) {
405 responseHeaders = (NSDictionary *)CFHTTPMessageCopyAllHeaderFields(headers);
fe503b0 Added basic unit tests
Ben Copsey authored
406 responseStatusCode = CFHTTPMessageGetResponseStatusCode(headers);
407
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
408 // Is the server response a challenge for credentials?
fe503b0 Added basic unit tests
Ben Copsey authored
409 isAuthenticationChallenge = (responseStatusCode == 401);
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
410
411 //We won't reset the download progress delegate if we got an authentication challenge
412 if (!isAuthenticationChallenge) {
413
414 //See if we got a Content-length header
415 NSString *cLength = [responseHeaders valueForKey:@"Content-Length"];
416 if (cLength) {
417 contentLength = CFStringGetDoubleValue((CFStringRef)cLength);
418 if (downloadProgressDelegate) {
419 [self performSelectorOnMainThread:@selector(resetDownloadProgress:) withObject:[NSNumber numberWithDouble:contentLength] waitUntilDone:YES];
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
420 }
421 }
2e48864 First public release
Ben Copsey authored
422 }
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
423
424 }
425 CFRelease(headers);
2e48864 First public release
Ben Copsey authored
426 return isAuthenticationChallenge;
427 }
428
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
429
430 - (void)saveCredentialsToKeychain:(NSMutableDictionary *)newCredentials
431 {
432 NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername]
433 password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent];
434
435 if (authenticationCredentials) {
436 [ASIHTTPRequest saveCredentials:authenticationCredentials forHost:[url host] port:[[url port] intValue] protocol:[url scheme] realm:authenticationRealm];
437 }
438 }
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
439
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
440 - (BOOL)applyCredentials:(NSMutableDictionary *)newCredentials
441 {
442
fe503b0 Added basic unit tests
Ben Copsey authored
443 if (newCredentials && requestAuthentication && request) {
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
444 // Apply whatever credentials we've built up to the old request
fe503b0 Added basic unit tests
Ben Copsey authored
445 if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
446 //If we have credentials and they're ok, let's save them to the keychain
447 if (useKeychainPersistance) {
448 [self saveCredentialsToKeychain:newCredentials];
449 }
450 if (useSessionPersistance) {
451
fe503b0 Added basic unit tests
Ben Copsey authored
452 [ASIHTTPRequest setSessionAuthentication:requestAuthentication];
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
453 [ASIHTTPRequest setSessionCredentials:newCredentials];
2e48864 First public release
Ben Copsey authored
454 }
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
455 [self setRequestCredentials:newCredentials];
456 return TRUE;
2e48864 First public release
Ben Copsey authored
457 }
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
458 }
459 return FALSE;
2e48864 First public release
Ben Copsey authored
460 }
461
fe503b0 Added basic unit tests
Ben Copsey authored
462 - (NSMutableDictionary *)findCredentials
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
463 {
464 NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease];
465
466 // Get the authentication realm
467 [authenticationRealm release];
468 authenticationRealm = nil;
fe503b0 Added basic unit tests
Ben Copsey authored
469 if (!CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
470 authenticationRealm = (NSString *)CFHTTPAuthenticationCopyRealm(requestAuthentication);
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
471 }
472
473 //First, let's look at the url to see if the username and password were included
474 NSString *user = [url user];
475 NSString *pass = [url password];
476
477 //If the username and password weren't in the url, let's try to use the ones set in this object
478 if ((!user || !pass) && username && password) {
479 user = username;
480 pass = password;
481 }
482
483 //Ok, that didn't work, let's try the keychain
484 if ((!user || !pass) && useKeychainPersistance) {
485 NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForHost:[url host] port:443 protocol:[url scheme] realm:authenticationRealm];
486 if (authenticationCredentials) {
487 user = [authenticationCredentials user];
488 pass = [authenticationCredentials password];
489 }
490
491 }
492
493 //If we have a username and password, let's apply them to the request and continue
494 if (user && pass) {
495
496 [newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername];
497 [newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword];
498 return newCredentials;
499 }
fe503b0 Added basic unit tests
Ben Copsey authored
500 return nil;
501 }
502
503 // Called by delegate to resume loading once authentication info has been populated
504 - (void)retryWithAuthentication
505 {
506 [authenticationLock lockWhenCondition:1];
507 [authenticationLock unlockWithCondition:2];
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
508 }
2e48864 First public release
Ben Copsey authored
509
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
510 - (void)attemptToApplyCredentialsAndResume
2e48864 First public release
Ben Copsey authored
511 {
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
512
513 //Read authentication data
fe503b0 Added basic unit tests
Ben Copsey authored
514 if (!requestAuthentication) {
2e48864 First public release
Ben Copsey authored
515 CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream,kCFStreamPropertyHTTPResponseHeader);
fe503b0 Added basic unit tests
Ben Copsey authored
516 requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
2e48864 First public release
Ben Copsey authored
517 CFRelease(responseHeader);
518 }
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
519
fe503b0 Added basic unit tests
Ben Copsey authored
520 if (!requestAuthentication) {
2e48864 First public release
Ben Copsey authored
521 [self failWithProblem:@"Failed to get authentication object from response headers"];
522 return;
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
523 }
2e48864 First public release
Ben Copsey authored
524
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
525 //See if authentication is valid
526 CFStreamError err;
fe503b0 Added basic unit tests
Ben Copsey authored
527 if (!CFHTTPAuthenticationIsValid(requestAuthentication, &err)) {
2e48864 First public release
Ben Copsey authored
528
fe503b0 Added basic unit tests
Ben Copsey authored
529 CFRelease(requestAuthentication);
530 requestAuthentication = NULL;
2e48864 First public release
Ben Copsey authored
531
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
532 // check for bad credentials, so we can give the delegate a chance to replace them
2e48864 First public release
Ben Copsey authored
533 if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) {
534 ignoreError = YES;
535 if ([delegate respondsToSelector:@selector(authorizationNeededForRequest:)]) {
536 [delegate performSelectorOnMainThread:@selector(authorizationNeededForRequest:) withObject:self waitUntilDone:YES];
537 [authenticationLock lockWhenCondition:2];
538 [authenticationLock unlock];
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
539
540 //Hopefully, the delegate gave us some credentials, let's apply them and reload
541 [self attemptToApplyCredentialsAndResume];
2e48864 First public release
Ben Copsey authored
542 return;
543 }
544 }
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
545 [self setError:[self authenticationError]];
546 complete = YES;
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
547 return;
548 }
2e48864 First public release
Ben Copsey authored
549
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
550 [self cancelLoad];
551
552 if (requestCredentials) {
553 if ([self applyCredentials:requestCredentials]) {
554 [self loadRequest];
555 } else {
556 [self failWithProblem:@"Failed to apply credentials to request"];
557 }
558
559 // are a user name & password needed?
fe503b0 Added basic unit tests
Ben Copsey authored
560 } else if (CFHTTPAuthenticationRequiresUserNameAndPassword(requestAuthentication)) {
2e48864 First public release
Ben Copsey authored
561
fe503b0 Added basic unit tests
Ben Copsey authored
562 NSMutableDictionary *newCredentials = [self findCredentials];
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
563
564 //If we have some credentials to use let's apply them to the request and continue
565 if (newCredentials) {
2e48864 First public release
Ben Copsey authored
566
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
567 if ([self applyCredentials:newCredentials]) {
568 [self loadRequest];
569 } else {
570 [self failWithProblem:@"Failed to apply credentials to request"];
2e48864 First public release
Ben Copsey authored
571 }
572 return;
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
573 }
2e48864 First public release
Ben Copsey authored
574
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
575 //We've got no credentials, let's ask the delegate to sort this out
576 ignoreError = YES;
577 if ([delegate respondsToSelector:@selector(authorizationNeededForRequest:)]) {
578 [delegate performSelectorOnMainThread:@selector(authorizationNeededForRequest:) withObject:self waitUntilDone:YES];
579 [authenticationLock lockWhenCondition:2];
580 [authenticationLock unlock];
581 [self attemptToApplyCredentialsAndResume];
582 return;
2e48864 First public release
Ben Copsey authored
583 }
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
584
585 //The delegate isn't interested, we'll have to give up
586 [self setError:[self authenticationError]];
587 complete = YES;
588 return;
589 }
590
2e48864 First public release
Ben Copsey authored
591 }
592
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
593 - (NSError *)authenticationError
594 {
595 return [NSError errorWithDomain:NetworkRequestErrorDomain
596 code:2
597 userInfo:[NSDictionary dictionaryWithObjectsAndKeys: @"Permission Denied",@"Title",
598 @"Your username and password were incorrect.",@"Description",nil]];
599
600 }
601
2e48864 First public release
Ben Copsey authored
602
603 #pragma mark stream status handlers
604
605
606 - (void)handleNetworkEvent:(CFStreamEventType)type
607 {
608
609 // Dispatch the stream events.
610 switch (type) {
611 case kCFStreamEventHasBytesAvailable:
612 [self handleBytesAvailable];
613 break;
614
615 case kCFStreamEventEndEncountered:
616 [self handleStreamComplete];
617 break;
618
619 case kCFStreamEventErrorOccurred:
620 [self handleStreamError];
621 break;
622
623 default:
624 break;
625 }
626 }
627
628
629 - (void)handleBytesAvailable
630 {
631
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
632 if (!responseHeaders) {
633 if ([self readResponseHeadersReturningAuthenticationFailure]) {
634 [self attemptToApplyCredentialsAndResume];
2e48864 First public release
Ben Copsey authored
635 return;
636 }
637 }
638
639 UInt8 buffer[2048];
640 CFIndex bytesRead = CFReadStreamRead(readStream, buffer, sizeof(buffer));
641
642
643 // Less than zero is an error
644 if (bytesRead < 0) {
645 [self handleStreamError];
646
647 // If zero bytes were read, wait for the EOF to come.
648 } else if (bytesRead) {
649
650 totalBytesRead += bytesRead;
651
652 // Are we downloading to a file?
653 if (downloadDestinationPath) {
654 if (!outputStream) {
655 outputStream = [[NSOutputStream alloc] initToFileAtPath:downloadDestinationPath append:NO];
656 [outputStream open];
657 }
658 [outputStream write:buffer maxLength:bytesRead];
659
660 //Otherwise, let's add the data to our in-memory store
661 } else {
662 CFDataAppendBytes(receivedData, buffer, bytesRead);
663 }
664 }
665 }
666
667
668 - (void)handleStreamComplete
669 {
670 complete = YES;
671 [self updateUploadProgress];
672 [self updateDownloadProgress];
673 if (readStream) {
674 CFReadStreamClose(readStream);
675 CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
676 CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
677 CFRelease(readStream);
678 readStream = NULL;
679 }
680
681 //close the output stream as we're done writing to the file
682 if (downloadDestinationPath) {
683 [outputStream close];
684 }
685
686 [self requestFinished];
687 }
688
689
690 - (void)handleStreamError
691 {
692 complete = YES;
693 NSError *err = [(NSError *)CFReadStreamCopyError(readStream) autorelease];
694
695 [self cancelLoad];
696
697 if (!error) { //We may already have handled this error
698 [self failWithProblem:[NSString stringWithFormat: @"An error occurred: %@",[err localizedDescription]]];
699 }
700 }
701
fe503b0 Added basic unit tests
Ben Copsey authored
702 #pragma mark managing the session
703
704 + (void)setSessionCredentials:(NSMutableDictionary *)newCredentials
705 {
706 [sessionCredentials release];
707 sessionCredentials = [newCredentials retain];
708 }
709
710 + (void)setSessionAuthentication:(CFHTTPAuthenticationRef)newAuthentication
711 {
712 if (sessionAuthentication) {
713 CFRelease(sessionAuthentication);
714 }
715 sessionAuthentication = newAuthentication;
716 if (newAuthentication) {
717 CFRetain(sessionAuthentication);
718 }
719 }
2e48864 First public release
Ben Copsey authored
720
721 #pragma mark keychain storage
722
723 + (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
724 {
725 NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host
726 port:port
727 protocol:protocol
728 realm:realm
729 authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
730
731
732 NSURLCredentialStorage *storage = [NSURLCredentialStorage sharedCredentialStorage];
733 [storage setDefaultCredential:credentials forProtectionSpace:protectionSpace];
734 }
735
736 + (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
737 {
738 NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host
739 port:port
740 protocol:protocol
741 realm:realm
742 authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
743
744
745 NSURLCredentialStorage *storage = [NSURLCredentialStorage sharedCredentialStorage];
746 return [storage defaultCredentialForProtectionSpace:protectionSpace];
747 }
748
c63b5e1 Stop using an NSTimer to update progress, we just do it from the main…
Ben Copsey authored
749 + (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
750 {
751 NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host
752 port:port
753 protocol:protocol
754 realm:realm
755 authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
756
757
758 NSURLCredentialStorage *storage = [NSURLCredentialStorage sharedCredentialStorage];
759 [storage removeCredential:[storage defaultCredentialForProtectionSpace:protectionSpace] forProtectionSpace:protectionSpace];
760
761 }
2e48864 First public release
Ben Copsey authored
762
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
763
764
765
753b0fb Convert getters / setters to obj-c 2.0 properties
Ben Copsey authored
766 @synthesize url;
767 @synthesize delegate;
768 @synthesize uploadProgressDelegate;
769 @synthesize downloadProgressDelegate;
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
770 @synthesize useKeychainPersistance;
771 @synthesize useSessionPersistance;
753b0fb Convert getters / setters to obj-c 2.0 properties
Ben Copsey authored
772 @synthesize downloadDestinationPath;
773 @synthesize didFinishSelector;
774 @synthesize didFailSelector;
775 @synthesize authenticationRealm;
776 @synthesize error;
777 @synthesize complete;
abdaa62 Reuse authentiction information in the session, removing the need to …
Ben Copsey authored
778 @synthesize responseHeaders;
779 @synthesize requestCredentials;
fe503b0 Added basic unit tests
Ben Copsey authored
780 @synthesize responseStatusCode;
2e48864 First public release
Ben Copsey authored
781 @end
Something went wrong with that request. Please try again.