2012-08-25 10:38:29 +00:00
|
|
|
/* Copyright (c) 2010 Google Inc.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
//
|
|
|
|
// GTMHTTPFetchHistory.m
|
|
|
|
//
|
|
|
|
|
|
|
|
#define GTMHTTPFETCHHISTORY_DEFINE_GLOBALS 1
|
|
|
|
|
|
|
|
#import "GTMHTTPFetchHistory.h"
|
|
|
|
|
|
|
|
const NSTimeInterval kCachedURLReservationInterval = 60.0; // 1 minute
|
|
|
|
static NSString* const kGTMIfNoneMatchHeader = @"If-None-Match";
|
|
|
|
static NSString* const kGTMETagHeader = @"Etag";
|
|
|
|
|
|
|
|
@implementation GTMCookieStorage
|
|
|
|
|
|
|
|
- (id)init {
|
|
|
|
self = [super init];
|
|
|
|
if (self != nil) {
|
|
|
|
cookies_ = [[NSMutableArray alloc] init];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc {
|
|
|
|
[cookies_ release];
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// Add all cookies in the new cookie array to the storage,
|
|
|
|
// replacing stored cookies as appropriate.
|
2012-08-25 10:38:29 +00:00
|
|
|
//
|
2013-01-31 21:22:37 +00:00
|
|
|
// Side effect: removes expired cookies from the storage array.
|
2012-08-25 10:38:29 +00:00
|
|
|
- (void)setCookies:(NSArray *)newCookies {
|
|
|
|
|
|
|
|
@synchronized(cookies_) {
|
|
|
|
[self removeExpiredCookies];
|
|
|
|
|
|
|
|
for (NSHTTPCookie *newCookie in newCookies) {
|
|
|
|
if ([[newCookie name] length] > 0
|
|
|
|
&& [[newCookie domain] length] > 0
|
|
|
|
&& [[newCookie path] length] > 0) {
|
|
|
|
|
|
|
|
// remove the cookie if it's currently in the array
|
|
|
|
NSHTTPCookie *oldCookie = [self cookieMatchingCookie:newCookie];
|
|
|
|
if (oldCookie) {
|
|
|
|
[cookies_ removeObjectIdenticalTo:oldCookie];
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure the cookie hasn't already expired
|
|
|
|
NSDate *expiresDate = [newCookie expiresDate];
|
|
|
|
if ((!expiresDate) || [expiresDate timeIntervalSinceNow] > 0) {
|
|
|
|
[cookies_ addObject:newCookie];
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
NSAssert1(NO, @"Cookie incomplete: %@", newCookie);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)deleteCookie:(NSHTTPCookie *)cookie {
|
|
|
|
@synchronized(cookies_) {
|
|
|
|
NSHTTPCookie *foundCookie = [self cookieMatchingCookie:cookie];
|
|
|
|
if (foundCookie) {
|
|
|
|
[cookies_ removeObjectIdenticalTo:foundCookie];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// Retrieve all cookies appropriate for the given URL, considering
|
2012-08-25 10:38:29 +00:00
|
|
|
// domain, path, cookie name, expiration, security setting.
|
2013-01-31 21:22:37 +00:00
|
|
|
// Side effect: removed expired cookies from the storage array.
|
2012-08-25 10:38:29 +00:00
|
|
|
- (NSArray *)cookiesForURL:(NSURL *)theURL {
|
|
|
|
|
|
|
|
NSMutableArray *foundCookies = nil;
|
|
|
|
|
|
|
|
@synchronized(cookies_) {
|
|
|
|
[self removeExpiredCookies];
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// We'll prepend "." to the desired domain, since we want the
|
2012-08-25 10:38:29 +00:00
|
|
|
// actual domain "nytimes.com" to still match the cookie domain
|
2013-01-31 21:22:37 +00:00
|
|
|
// ".nytimes.com" when we check it below with hasSuffix.
|
2012-08-25 10:38:29 +00:00
|
|
|
NSString *host = [[theURL host] lowercaseString];
|
|
|
|
NSString *path = [theURL path];
|
|
|
|
NSString *scheme = [theURL scheme];
|
|
|
|
|
|
|
|
NSString *domain = nil;
|
|
|
|
BOOL isLocalhostRetrieval = NO;
|
|
|
|
|
|
|
|
if ([host isEqual:@"localhost"]) {
|
|
|
|
isLocalhostRetrieval = YES;
|
|
|
|
} else {
|
|
|
|
if (host) {
|
|
|
|
domain = [@"." stringByAppendingString:host];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NSUInteger numberOfCookies = [cookies_ count];
|
|
|
|
for (NSUInteger idx = 0; idx < numberOfCookies; idx++) {
|
|
|
|
|
|
|
|
NSHTTPCookie *storedCookie = [cookies_ objectAtIndex:idx];
|
|
|
|
|
|
|
|
NSString *cookieDomain = [[storedCookie domain] lowercaseString];
|
|
|
|
NSString *cookiePath = [storedCookie path];
|
|
|
|
BOOL cookieIsSecure = [storedCookie isSecure];
|
|
|
|
|
|
|
|
BOOL isDomainOK;
|
|
|
|
|
|
|
|
if (isLocalhostRetrieval) {
|
|
|
|
// prior to 10.5.6, the domain stored into NSHTTPCookies for localhost
|
|
|
|
// is "localhost.local"
|
|
|
|
isDomainOK = [cookieDomain isEqual:@"localhost"]
|
|
|
|
|| [cookieDomain isEqual:@"localhost.local"];
|
|
|
|
} else {
|
|
|
|
isDomainOK = [domain hasSuffix:cookieDomain];
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL isPathOK = [cookiePath isEqual:@"/"] || [path hasPrefix:cookiePath];
|
|
|
|
BOOL isSecureOK = (!cookieIsSecure) || [scheme isEqual:@"https"];
|
|
|
|
|
|
|
|
if (isDomainOK && isPathOK && isSecureOK) {
|
|
|
|
if (foundCookies == nil) {
|
|
|
|
foundCookies = [NSMutableArray arrayWithCapacity:1];
|
|
|
|
}
|
|
|
|
[foundCookies addObject:storedCookie];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return foundCookies;
|
|
|
|
}
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// Return a cookie from the array with the same name, domain, and path as the
|
|
|
|
// given cookie, or else return nil if none found.
|
2012-08-25 10:38:29 +00:00
|
|
|
//
|
|
|
|
// Both the cookie being tested and all cookies in the storage array should
|
2013-01-31 21:22:37 +00:00
|
|
|
// be valid (non-nil name, domains, paths).
|
2012-08-25 10:38:29 +00:00
|
|
|
//
|
2013-01-31 21:22:37 +00:00
|
|
|
// Note: this should only be called from inside a @synchronized(cookies_) block
|
2012-08-25 10:38:29 +00:00
|
|
|
- (NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie {
|
|
|
|
|
|
|
|
NSUInteger numberOfCookies = [cookies_ count];
|
|
|
|
NSString *name = [cookie name];
|
|
|
|
NSString *domain = [cookie domain];
|
|
|
|
NSString *path = [cookie path];
|
|
|
|
|
|
|
|
NSAssert3(name && domain && path, @"Invalid cookie (name:%@ domain:%@ path:%@)",
|
|
|
|
name, domain, path);
|
|
|
|
|
|
|
|
for (NSUInteger idx = 0; idx < numberOfCookies; idx++) {
|
|
|
|
|
|
|
|
NSHTTPCookie *storedCookie = [cookies_ objectAtIndex:idx];
|
|
|
|
|
|
|
|
if ([[storedCookie name] isEqual:name]
|
|
|
|
&& [[storedCookie domain] isEqual:domain]
|
|
|
|
&& [[storedCookie path] isEqual:path]) {
|
|
|
|
|
|
|
|
return storedCookie;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// Internal routine to remove any expired cookies from the array, excluding
|
|
|
|
// cookies with nil expirations.
|
2012-08-25 10:38:29 +00:00
|
|
|
//
|
2013-01-31 21:22:37 +00:00
|
|
|
// Note: this should only be called from inside a @synchronized(cookies_) block
|
2012-08-25 10:38:29 +00:00
|
|
|
- (void)removeExpiredCookies {
|
|
|
|
|
|
|
|
// count backwards since we're deleting items from the array
|
|
|
|
for (NSInteger idx = [cookies_ count] - 1; idx >= 0; idx--) {
|
|
|
|
|
|
|
|
NSHTTPCookie *storedCookie = [cookies_ objectAtIndex:idx];
|
|
|
|
|
|
|
|
NSDate *expiresDate = [storedCookie expiresDate];
|
|
|
|
if (expiresDate && [expiresDate timeIntervalSinceNow] < 0) {
|
|
|
|
[cookies_ removeObjectAtIndex:idx];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)removeAllCookies {
|
|
|
|
@synchronized(cookies_) {
|
|
|
|
[cookies_ removeAllObjects];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
//
|
|
|
|
// GTMCachedURLResponse
|
|
|
|
//
|
|
|
|
|
|
|
|
@implementation GTMCachedURLResponse
|
|
|
|
|
|
|
|
@synthesize response = response_;
|
|
|
|
@synthesize data = data_;
|
|
|
|
@synthesize reservationDate = reservationDate_;
|
|
|
|
@synthesize useDate = useDate_;
|
|
|
|
|
|
|
|
- (id)initWithResponse:(NSURLResponse *)response data:(NSData *)data {
|
|
|
|
self = [super init];
|
|
|
|
if (self != nil) {
|
|
|
|
response_ = [response retain];
|
|
|
|
data_ = [data retain];
|
|
|
|
useDate_ = [[NSDate alloc] init];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc {
|
|
|
|
[response_ release];
|
|
|
|
[data_ release];
|
|
|
|
[useDate_ release];
|
|
|
|
[reservationDate_ release];
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)description {
|
|
|
|
NSString *reservationStr = reservationDate_ ?
|
|
|
|
[NSString stringWithFormat:@" resDate:%@", reservationDate_] : @"";
|
|
|
|
|
|
|
|
return [NSString stringWithFormat:@"%@ %p: {bytes:%@ useDate:%@%@}",
|
|
|
|
[self class], self,
|
|
|
|
data_ ? [NSNumber numberWithInt:(int)[data_ length]] : nil,
|
|
|
|
useDate_,
|
|
|
|
reservationStr];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSComparisonResult)compareUseDate:(GTMCachedURLResponse *)other {
|
|
|
|
return [useDate_ compare:[other useDate]];
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
//
|
|
|
|
// GTMURLCache
|
|
|
|
//
|
|
|
|
|
|
|
|
@implementation GTMURLCache
|
|
|
|
|
|
|
|
@dynamic memoryCapacity;
|
|
|
|
|
|
|
|
- (id)init {
|
|
|
|
return [self initWithMemoryCapacity:kGTMDefaultETaggedDataCacheMemoryCapacity];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)initWithMemoryCapacity:(NSUInteger)totalBytes {
|
|
|
|
self = [super init];
|
|
|
|
if (self != nil) {
|
|
|
|
memoryCapacity_ = totalBytes;
|
|
|
|
|
|
|
|
responses_ = [[NSMutableDictionary alloc] initWithCapacity:5];
|
|
|
|
|
|
|
|
reservationInterval_ = kCachedURLReservationInterval;
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc {
|
|
|
|
[responses_ release];
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)description {
|
|
|
|
return [NSString stringWithFormat:@"%@ %p: {responses:%@}",
|
|
|
|
[self class], self, [responses_ allValues]];
|
|
|
|
}
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// Setters/getters
|
2012-08-25 10:38:29 +00:00
|
|
|
|
|
|
|
- (void)pruneCacheResponses {
|
2013-01-31 21:22:37 +00:00
|
|
|
// Internal routine to remove the least-recently-used responses when the
|
2012-08-25 10:38:29 +00:00
|
|
|
// cache has grown too large
|
|
|
|
if (memoryCapacity_ >= totalDataSize_) return;
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// Sort keys by date
|
2012-08-25 10:38:29 +00:00
|
|
|
SEL sel = @selector(compareUseDate:);
|
|
|
|
NSArray *sortedKeys = [responses_ keysSortedByValueUsingSelector:sel];
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// The least-recently-used keys are at the beginning of the sorted array;
|
2012-08-25 10:38:29 +00:00
|
|
|
// remove those (except ones still reserved) until the total data size is
|
|
|
|
// reduced sufficiently
|
|
|
|
for (NSURL *key in sortedKeys) {
|
|
|
|
GTMCachedURLResponse *response = [responses_ objectForKey:key];
|
|
|
|
|
|
|
|
NSDate *resDate = [response reservationDate];
|
|
|
|
BOOL isResponseReserved = (resDate != nil)
|
|
|
|
&& ([resDate timeIntervalSinceNow] > -reservationInterval_);
|
|
|
|
|
|
|
|
if (!isResponseReserved) {
|
2013-01-31 21:22:37 +00:00
|
|
|
// We can remove this response from the cache
|
2012-08-25 10:38:29 +00:00
|
|
|
NSUInteger storedSize = [[response data] length];
|
|
|
|
totalDataSize_ -= storedSize;
|
|
|
|
[responses_ removeObjectForKey:key];
|
|
|
|
}
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// If we've removed enough response data, then we're done
|
2012-08-25 10:38:29 +00:00
|
|
|
if (memoryCapacity_ >= totalDataSize_) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)storeCachedResponse:(GTMCachedURLResponse *)cachedResponse
|
|
|
|
forRequest:(NSURLRequest *)request {
|
|
|
|
@synchronized(self) {
|
2013-01-31 21:22:37 +00:00
|
|
|
// Remove any previous entry for this request
|
2012-08-25 10:38:29 +00:00
|
|
|
[self removeCachedResponseForRequest:request];
|
|
|
|
|
|
|
|
// cache this one only if it's not bigger than our cache
|
|
|
|
NSUInteger storedSize = [[cachedResponse data] length];
|
|
|
|
if (storedSize < memoryCapacity_) {
|
|
|
|
|
|
|
|
NSURL *key = [request URL];
|
|
|
|
[responses_ setObject:cachedResponse forKey:key];
|
|
|
|
totalDataSize_ += storedSize;
|
|
|
|
|
|
|
|
[self pruneCacheResponses];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (GTMCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
|
|
|
|
GTMCachedURLResponse *response;
|
|
|
|
|
|
|
|
@synchronized(self) {
|
|
|
|
NSURL *key = [request URL];
|
|
|
|
response = [[[responses_ objectForKey:key] retain] autorelease];
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// Touch the date to indicate this was recently retrieved
|
2012-08-25 10:38:29 +00:00
|
|
|
[response setUseDate:[NSDate date]];
|
|
|
|
}
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)removeCachedResponseForRequest:(NSURLRequest *)request {
|
|
|
|
@synchronized(self) {
|
|
|
|
NSURL *key = [request URL];
|
|
|
|
totalDataSize_ -= [[[responses_ objectForKey:key] data] length];
|
|
|
|
[responses_ removeObjectForKey:key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)removeAllCachedResponses {
|
|
|
|
@synchronized(self) {
|
|
|
|
[responses_ removeAllObjects];
|
|
|
|
totalDataSize_ = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSUInteger)memoryCapacity {
|
|
|
|
return memoryCapacity_;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setMemoryCapacity:(NSUInteger)totalBytes {
|
|
|
|
@synchronized(self) {
|
|
|
|
BOOL didShrink = (totalBytes < memoryCapacity_);
|
|
|
|
memoryCapacity_ = totalBytes;
|
|
|
|
|
|
|
|
if (didShrink) {
|
|
|
|
[self pruneCacheResponses];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// Methods for unit testing.
|
2012-08-25 10:38:29 +00:00
|
|
|
- (void)setReservationInterval:(NSTimeInterval)secs {
|
|
|
|
reservationInterval_ = secs;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *)responses {
|
|
|
|
return responses_;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSUInteger)totalDataSize {
|
|
|
|
return totalDataSize_;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
//
|
|
|
|
// GTMHTTPFetchHistory
|
|
|
|
//
|
|
|
|
|
|
|
|
@interface GTMHTTPFetchHistory ()
|
|
|
|
- (NSString *)cachedETagForRequest:(NSURLRequest *)request;
|
|
|
|
- (void)removeCachedDataForRequest:(NSURLRequest *)request;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GTMHTTPFetchHistory
|
|
|
|
|
|
|
|
@synthesize cookieStorage = cookieStorage_;
|
|
|
|
|
|
|
|
@dynamic shouldRememberETags;
|
|
|
|
@dynamic shouldCacheETaggedData;
|
|
|
|
@dynamic memoryCapacity;
|
|
|
|
|
|
|
|
- (id)init {
|
|
|
|
return [self initWithMemoryCapacity:kGTMDefaultETaggedDataCacheMemoryCapacity
|
|
|
|
shouldCacheETaggedData:NO];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)initWithMemoryCapacity:(NSUInteger)totalBytes
|
|
|
|
shouldCacheETaggedData:(BOOL)shouldCacheETaggedData {
|
|
|
|
self = [super init];
|
|
|
|
if (self != nil) {
|
|
|
|
etaggedDataCache_ = [[GTMURLCache alloc] initWithMemoryCapacity:totalBytes];
|
|
|
|
shouldRememberETags_ = shouldCacheETaggedData;
|
|
|
|
shouldCacheETaggedData_ = shouldCacheETaggedData;
|
|
|
|
cookieStorage_ = [[GTMCookieStorage alloc] init];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc {
|
|
|
|
[etaggedDataCache_ release];
|
|
|
|
[cookieStorage_ release];
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateRequest:(NSMutableURLRequest *)request isHTTPGet:(BOOL)isHTTPGet {
|
2013-01-31 21:22:37 +00:00
|
|
|
@synchronized(self) {
|
|
|
|
if ([self shouldRememberETags]) {
|
|
|
|
// If this URL is in the history, and no ETag has been set, then
|
|
|
|
// set the ETag header field
|
|
|
|
|
|
|
|
// If we have a history, we're tracking across fetches, so we don't
|
|
|
|
// want to pull results from any other cache
|
|
|
|
[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
|
|
|
|
|
|
|
|
if (isHTTPGet) {
|
|
|
|
// We'll only add an ETag if there's no ETag specified in the user's
|
|
|
|
// request
|
|
|
|
NSString *specifiedETag = [request valueForHTTPHeaderField:kGTMIfNoneMatchHeader];
|
|
|
|
if (specifiedETag == nil) {
|
|
|
|
// No ETag: extract the previous ETag for this request from the
|
|
|
|
// fetch history, and add it to the request
|
|
|
|
NSString *cachedETag = [self cachedETagForRequest:request];
|
|
|
|
|
|
|
|
if (cachedETag != nil) {
|
|
|
|
[request addValue:cachedETag forHTTPHeaderField:kGTMIfNoneMatchHeader];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Has an ETag: remove any stored response in the fetch history
|
|
|
|
// for this request, as the If-None-Match header could lead to
|
|
|
|
// a 304 Not Modified, and we want that error delivered to the
|
|
|
|
// user since they explicitly specified the ETag
|
|
|
|
[self removeCachedDataForRequest:request];
|
2012-08-25 10:38:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)updateFetchHistoryWithRequest:(NSURLRequest *)request
|
|
|
|
response:(NSURLResponse *)response
|
|
|
|
downloadedData:(NSData *)downloadedData {
|
2013-01-31 21:22:37 +00:00
|
|
|
@synchronized(self) {
|
|
|
|
if (![self shouldRememberETags]) return;
|
2012-08-25 10:38:29 +00:00
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
if (![response respondsToSelector:@selector(allHeaderFields)]) return;
|
2012-08-25 10:38:29 +00:00
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
|
2012-08-25 10:38:29 +00:00
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
if (statusCode != kGTMHTTPFetcherStatusNotModified) {
|
|
|
|
// Save this ETag string for successful results (<300)
|
|
|
|
// If there's no last modified string, clear the dictionary
|
|
|
|
// entry for this URL. Also cache or delete the data, if appropriate
|
|
|
|
// (when etaggedDataCache is non-nil.)
|
|
|
|
NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
|
|
|
|
NSString* etag = [headers objectForKey:kGTMETagHeader];
|
2012-08-25 10:38:29 +00:00
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
if (etag != nil && statusCode < 300) {
|
2012-08-25 10:38:29 +00:00
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// we want to cache responses for the headers, even if the client
|
|
|
|
// doesn't want the response body data caches
|
|
|
|
NSData *dataToStore = shouldCacheETaggedData_ ? downloadedData : nil;
|
2012-08-25 10:38:29 +00:00
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
GTMCachedURLResponse *cachedResponse;
|
|
|
|
cachedResponse = [[[GTMCachedURLResponse alloc] initWithResponse:response
|
|
|
|
data:dataToStore] autorelease];
|
|
|
|
[etaggedDataCache_ storeCachedResponse:cachedResponse
|
|
|
|
forRequest:request];
|
|
|
|
} else {
|
|
|
|
[etaggedDataCache_ removeCachedResponseForRequest:request];
|
|
|
|
}
|
2012-08-25 10:38:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)cachedETagForRequest:(NSURLRequest *)request {
|
2013-01-31 21:22:37 +00:00
|
|
|
// Internal routine.
|
2012-08-25 10:38:29 +00:00
|
|
|
GTMCachedURLResponse *cachedResponse;
|
|
|
|
cachedResponse = [etaggedDataCache_ cachedResponseForRequest:request];
|
|
|
|
|
|
|
|
NSURLResponse *response = [cachedResponse response];
|
|
|
|
NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
|
|
|
|
NSString *cachedETag = [headers objectForKey:kGTMETagHeader];
|
|
|
|
if (cachedETag) {
|
2013-01-31 21:22:37 +00:00
|
|
|
// Since the request having an ETag implies this request is about
|
2012-08-25 10:38:29 +00:00
|
|
|
// to be fetched again, reserve the cached response to ensure that
|
2013-01-31 21:22:37 +00:00
|
|
|
// that it will be around at least until the fetch completes.
|
2012-08-25 10:38:29 +00:00
|
|
|
//
|
2013-01-31 21:22:37 +00:00
|
|
|
// When the fetch completes, either the cached response will be replaced
|
2012-08-25 10:38:29 +00:00
|
|
|
// with a new response, or the cachedDataForRequest: method below will
|
2013-01-31 21:22:37 +00:00
|
|
|
// clear the reservation.
|
2012-08-25 10:38:29 +00:00
|
|
|
[cachedResponse setReservationDate:[NSDate date]];
|
|
|
|
}
|
|
|
|
return cachedETag;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSData *)cachedDataForRequest:(NSURLRequest *)request {
|
2013-01-31 21:22:37 +00:00
|
|
|
@synchronized(self) {
|
|
|
|
GTMCachedURLResponse *cachedResponse;
|
|
|
|
cachedResponse = [etaggedDataCache_ cachedResponseForRequest:request];
|
2012-08-25 10:38:29 +00:00
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
NSData *cachedData = [cachedResponse data];
|
2012-08-25 10:38:29 +00:00
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
// Since the data for this cached request is being obtained from the cache,
|
|
|
|
// we can clear the reservation as the fetch has completed.
|
|
|
|
[cachedResponse setReservationDate:nil];
|
2012-08-25 10:38:29 +00:00
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
return cachedData;
|
|
|
|
}
|
2012-08-25 10:38:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)removeCachedDataForRequest:(NSURLRequest *)request {
|
2013-01-31 21:22:37 +00:00
|
|
|
@synchronized(self) {
|
|
|
|
[etaggedDataCache_ removeCachedResponseForRequest:request];
|
|
|
|
}
|
2012-08-25 10:38:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)clearETaggedDataCache {
|
2013-01-31 21:22:37 +00:00
|
|
|
@synchronized(self) {
|
|
|
|
[etaggedDataCache_ removeAllCachedResponses];
|
|
|
|
}
|
2012-08-25 10:38:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)clearHistory {
|
2013-01-31 21:22:37 +00:00
|
|
|
@synchronized(self) {
|
|
|
|
[self clearETaggedDataCache];
|
|
|
|
[cookieStorage_ removeAllCookies];
|
|
|
|
}
|
2012-08-25 10:38:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)removeAllCookies {
|
2013-01-31 21:22:37 +00:00
|
|
|
@synchronized(self) {
|
|
|
|
[cookieStorage_ removeAllCookies];
|
|
|
|
}
|
2012-08-25 10:38:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)shouldRememberETags {
|
|
|
|
return shouldRememberETags_;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setShouldRememberETags:(BOOL)flag {
|
|
|
|
BOOL wasRemembering = shouldRememberETags_;
|
|
|
|
shouldRememberETags_ = flag;
|
|
|
|
|
|
|
|
if (wasRemembering && !flag) {
|
2013-01-31 21:22:37 +00:00
|
|
|
// Free up the cache memory
|
2012-08-25 10:38:29 +00:00
|
|
|
[self clearETaggedDataCache];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)shouldCacheETaggedData {
|
|
|
|
return shouldCacheETaggedData_;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setShouldCacheETaggedData:(BOOL)flag {
|
|
|
|
BOOL wasCaching = shouldCacheETaggedData_;
|
|
|
|
shouldCacheETaggedData_ = flag;
|
|
|
|
|
|
|
|
if (flag) {
|
|
|
|
self.shouldRememberETags = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wasCaching && !flag) {
|
|
|
|
// users expect turning off caching to free up the cache memory
|
|
|
|
[self clearETaggedDataCache];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSUInteger)memoryCapacity {
|
|
|
|
return [etaggedDataCache_ memoryCapacity];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setMemoryCapacity:(NSUInteger)totalBytes {
|
|
|
|
[etaggedDataCache_ setMemoryCapacity:totalBytes];
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|