// // RKResponse.m // RestKit // // Created by Blake Watters on 7/28/09. // Copyright (c) 2009-2012 RestKit. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #import "RKResponse.h" #import "RKNotifications.h" #import "RKLog.h" #import "RKParserRegistry.h" #import "RKRequestCache.h" // Set Logging Component #undef RKLogComponent #define RKLogComponent lcl_cRestKitNetwork #define RKResponseIgnoreDelegateIfCancelled(...) \ if (self.request && [self.request isCancelled]) { \ RKLogDebug(@"%s: Ignoring NSURLConnection delegate message sent after cancel.", __PRETTY_FUNCTION__); \ return __VA_ARGS__; \ } @implementation RKResponse @synthesize body = _body, request = _request, failureError = _failureError; - (id)init { self = [super init]; if (self) { _body = [[NSMutableData alloc] init]; _failureError = nil; _loading = NO; _responseHeaders = nil; } return self; } - (id)initWithRequest:(RKRequest*)request { self = [self init]; if (self) { // We don't retain here as we're letting RKRequestQueue manage // request ownership _request = request; } return self; } - (id)initWithRequest:(RKRequest*)request body:(NSData*)body headers:(NSDictionary*)headers { self = [self initWithRequest:request]; if (self) { [_body release]; _body = [[NSMutableData dataWithData:body] retain]; _responseHeaders = [headers retain]; } return self; } - (id)initWithSynchronousRequest:(RKRequest*)request URLResponse:(NSHTTPURLResponse*)URLResponse body:(NSData*)body error:(NSError*)error { self = [super init]; if (self) { _request = request; _httpURLResponse = [URLResponse retain]; _failureError = [error retain]; _body = [[NSMutableData dataWithData:body] retain]; _loading = NO; } return self; } - (void)dealloc { _request = nil; [_httpURLResponse release]; _httpURLResponse = nil; [_body release]; _body = nil; [_failureError release]; _failureError = nil; [_responseHeaders release]; _responseHeaders = nil; [super dealloc]; } - (BOOL)hasCredentials { return _request.username && _request.password; } - (BOOL)isServerTrusted:(SecTrustRef)trust { BOOL proceed = NO; if (_request.disableCertificateValidation) { proceed = YES; } else if ([_request.additionalRootCertificates count] > 0 ) { CFArrayRef rootCerts = (CFArrayRef)[_request.additionalRootCertificates allObjects]; SecTrustResultType result; OSStatus returnCode; if (rootCerts && CFArrayGetCount(rootCerts)) { // this could fail, but the trust evaluation will proceed (it's likely to fail, of course) SecTrustSetAnchorCertificates(trust, rootCerts); } returnCode = SecTrustEvaluate(trust, &result); if (returnCode == errSecSuccess) { proceed = (result == kSecTrustResultProceed || result == kSecTrustResultConfirm || result == kSecTrustResultUnspecified); if (result == kSecTrustResultRecoverableTrustFailure) { // TODO: should try to recover here // call SecTrustGetCssmResult() for more information about the failure } } } return proceed; } // Handle basic auth & SSL certificate validation - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { RKResponseIgnoreDelegateIfCancelled(); RKLogDebug(@"Received authentication challenge"); if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { SecTrustRef trust = [[challenge protectionSpace] serverTrust]; if ([self isServerTrusted:trust]) { [challenge.sender useCredential:[NSURLCredential credentialForTrust:trust] forAuthenticationChallenge:challenge]; } else { [[challenge sender] cancelAuthenticationChallenge:challenge]; } return; } if ([challenge previousFailureCount] == 0) { NSURLCredential *newCredential; newCredential=[NSURLCredential credentialWithUser:[NSString stringWithFormat:@"%@", _request.username] password:[NSString stringWithFormat:@"%@", _request.password] persistence:NSURLCredentialPersistenceNone]; [[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge]; } else { RKLogWarning(@"Failed authentication challenge after %ld failures", (long) [challenge previousFailureCount]); [[challenge sender] cancelAuthenticationChallenge:challenge]; } } - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)space { RKResponseIgnoreDelegateIfCancelled(NO); RKLogDebug(@"Asked if canAuthenticateAgainstProtectionSpace: with authenticationMethod = %@", [space authenticationMethod]); if ([[space authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) { // server is using an SSL certificate that the OS can't validate // see whether the client settings allow validation here if (_request.disableCertificateValidation || [_request.additionalRootCertificates count] > 0) { return YES; } else { return NO; } } // Handle non-SSL challenges BOOL hasCredentials = [self hasCredentials]; if (! hasCredentials) { RKLogWarning(@"Received an authentication challenge without any credentials to satisfy the request."); } return hasCredentials; } - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response { if (nil == response || _request.followRedirect) { RKLogDebug(@"Proceeding with request to %@", request); return request; } else { RKLogDebug(@"Not following redirect to %@", request); return nil; } } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { RKResponseIgnoreDelegateIfCancelled(); [_body appendData:data]; [_request invalidateTimeoutTimer]; if ([[_request delegate] respondsToSelector:@selector(request:didReceiveData:totalBytesReceived:totalBytesExpectedToReceive:)]) { [[_request delegate] request:_request didReceiveData:[data length] totalBytesReceived:[_body length] totalBytesExpectedToReceive:_httpURLResponse.expectedContentLength]; } } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response { RKResponseIgnoreDelegateIfCancelled(); RKLogDebug(@"NSHTTPURLResponse Status Code: %ld", (long) [response statusCode]); RKLogDebug(@"Headers: %@", [response allHeaderFields]); _httpURLResponse = [response retain]; [_request invalidateTimeoutTimer]; if ([[_request delegate] respondsToSelector:@selector(request:didReceiveResponse:)]) { [[_request delegate] request:_request didReceiveResponse:self]; } } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { RKResponseIgnoreDelegateIfCancelled(); RKLogTrace(@"Read response body: %@", [self bodyAsString]); [_request didFinishLoad:self]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { RKResponseIgnoreDelegateIfCancelled(); _failureError = [error retain]; [_request invalidateTimeoutTimer]; [_request didFailLoadWithError:_failureError]; } - (NSInputStream *)connection:(NSURLConnection *)connection needNewBodyStream:(NSURLRequest *)request { RKResponseIgnoreDelegateIfCancelled(nil); RKLogWarning(@"RestKit was asked to retransmit a new body stream for a request. Possible connection error or authentication challenge?"); return nil; } // In the event that the url request is a post, this delegate method will be called before // either connection:didReceiveData: or connection:didReceiveResponse: // However this method is only called if there is payload data to be sent. // Therefore, we ensure the delegate recieves the did start loading here and // in connection:didReceiveResponse: to ensure that the RKRequestDelegate // callbacks get called in the correct order. - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { RKResponseIgnoreDelegateIfCancelled(); [_request invalidateTimeoutTimer]; if ([[_request delegate] respondsToSelector:@selector(request:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:)]) { [[_request delegate] request:_request didSendBodyData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite]; } } - (NSString*)localizedStatusCodeString { return [NSHTTPURLResponse localizedStringForStatusCode:[self statusCode]]; } - (NSData *)body { return _body; } - (NSString *)bodyEncodingName { return [_httpURLResponse textEncodingName]; } - (NSStringEncoding)bodyEncoding { CFStringEncoding cfEncoding = kCFStringEncodingInvalidId; NSString *textEncodingName = [self bodyEncodingName]; if (textEncodingName) { cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef) textEncodingName); } return (cfEncoding == kCFStringEncodingInvalidId) ? self.request.defaultHTTPEncoding : CFStringConvertEncodingToNSStringEncoding(cfEncoding); } - (NSString *)bodyAsString { return [[[NSString alloc] initWithData:self.body encoding:[self bodyEncoding]] autorelease]; } - (id)bodyAsJSON { [NSException raise:nil format:@"Reimplemented as parsedBody"]; return nil; } - (id)parsedBody:(NSError**)error { id parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:[self MIMEType]]; if (! parser) { RKLogWarning(@"Unable to parse response body: no parser registered for MIME Type '%@'", [self MIMEType]); return nil; } id object = [parser objectFromString:[self bodyAsString] error:error]; if (object == nil) { if (error && *error) { RKLogError(@"Unable to parse response body: %@", [*error localizedDescription]); } return nil; } return object; } - (NSString*)failureErrorDescription { if ([self isFailure]) { return [_failureError localizedDescription]; } else { return nil; } } - (BOOL)wasLoadedFromCache { return (_responseHeaders != nil); } - (NSURL*)URL { if ([self wasLoadedFromCache]) { return [NSURL URLWithString:[_responseHeaders valueForKey:RKRequestCacheURLHeadersKey]]; } return [_httpURLResponse URL]; } - (NSString*)MIMEType { if ([self wasLoadedFromCache]) { return [_responseHeaders valueForKey:RKRequestCacheMIMETypeHeadersKey]; } return [_httpURLResponse MIMEType]; } - (NSInteger)statusCode { if ([self wasLoadedFromCache]) { return [[_responseHeaders valueForKey:RKRequestCacheStatusCodeHeadersKey] intValue]; } return ([_httpURLResponse respondsToSelector:@selector(statusCode)] ? [_httpURLResponse statusCode] : 200); } - (NSDictionary*)allHeaderFields { if ([self wasLoadedFromCache]) { return _responseHeaders; } return ([_httpURLResponse respondsToSelector:@selector(allHeaderFields)] ? [_httpURLResponse allHeaderFields] : nil); } - (NSArray*)cookies { return [NSHTTPCookie cookiesWithResponseHeaderFields:self.allHeaderFields forURL:self.URL]; } - (BOOL)isFailure { return (nil != _failureError); } - (BOOL)isInvalid { return ([self statusCode] < 100 || [self statusCode] > 600); } - (BOOL)isInformational { return ([self statusCode] >= 100 && [self statusCode] < 200); } - (BOOL)isSuccessful { return (([self statusCode] >= 200 && [self statusCode] < 300) || ([self wasLoadedFromCache])); } - (BOOL)isRedirection { return ([self statusCode] >= 300 && [self statusCode] < 400); } - (BOOL)isClientError { return ([self statusCode] >= 400 && [self statusCode] < 500); } - (BOOL)isServerError { return ([self statusCode] >= 500 && [self statusCode] < 600); } - (BOOL)isError { return ([self isClientError] || [self isServerError]); } - (BOOL)isOK { return ([self statusCode] == 200); } - (BOOL)isCreated { return ([self statusCode] == 201); } - (BOOL)isNoContent { return ([self statusCode] == 204); } - (BOOL)isNotModified { return ([self statusCode] == 304); } - (BOOL)isUnauthorized { return ([self statusCode] == 401); } - (BOOL)isForbidden { return ([self statusCode] == 403); } - (BOOL)isNotFound { return ([self statusCode] == 404); } - (BOOL)isConflict { return ([self statusCode] == 409); } - (BOOL)isGone { return ([self statusCode] == 410); } - (BOOL)isUnprocessableEntity { return ([self statusCode] == 422); } - (BOOL)isRedirect { return ([self statusCode] == 301 || [self statusCode] == 302 || [self statusCode] == 303 || [self statusCode] == 307); } - (BOOL)isEmpty { return ([self statusCode] == 201 || [self statusCode] == 204 || [self statusCode] == 304); } - (BOOL)isServiceUnavailable { return ([self statusCode] == 503); } - (NSString*)contentType { return ([[self allHeaderFields] objectForKey:@"Content-Type"]); } - (NSString*)contentLength { return ([[self allHeaderFields] objectForKey:@"Content-Length"]); } - (NSString*)location { return ([[self allHeaderFields] objectForKey:@"Location"]); } - (BOOL)isHTML { NSString* contentType = [self contentType]; return (contentType && ([contentType rangeOfString:@"text/html" options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0 || [self isXHTML])); } - (BOOL)isXHTML { NSString* contentType = [self contentType]; return (contentType && [contentType rangeOfString:@"application/xhtml+xml" options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0); } - (BOOL)isXML { NSString* contentType = [self contentType]; return (contentType && [contentType rangeOfString:@"application/xml" options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0); } - (BOOL)isJSON { NSString* contentType = [self contentType]; return (contentType && [contentType rangeOfString:@"application/json" options:NSCaseInsensitiveSearch|NSAnchoredSearch].length > 0); } @end