Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

523 lines (444 sloc) 15.503 kb
//
// 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;
@synthesize request = _request;
@synthesize 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<RKParser> 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
Jump to Line
Something went wrong with that request. Please try again.