Skip to content

Instantly share code, notes, and snippets.

@adib
Last active July 7, 2016 07:02
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adib/5341936 to your computer and use it in GitHub Desktop.
Save adib/5341936 to your computer and use it in GitHub Desktop.
Custom Cookie Storage, rev 2.
//
// BSHTTPCookieStorage.h
//
// Created by Sasmito Adibowo on 02-07-12.
// Copyright (c) 2012 Basil Salad Software. All rights reserved.
// http://basilsalad.com
//
// Licensed under the BSD License <http://www.opensource.org/licenses/bsd-license>
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/Foundation.h>
/**
Stores cookies.
*/
@interface BSHTTPCookieStorage : NSObject<NSCoding,NSCopying>
- (NSArray *)cookiesForURL:(NSURL *)theURL;
- (void)setCookie:(NSHTTPCookie *)aCookie;
- (void) removeCookiesNamed:(NSString*) cookieName;
/**
Removes all stored cookies from this storage
*/
-(void) reset;
-(void) loadCookies:(id<NSFastEnumeration>) cookies;
-(void) handleCookiesInRequest:(NSMutableURLRequest*) request;
-(void) handleCookiesInResponse:(NSHTTPURLResponse*) response;
-(void) handleWebScriptCookies:(NSString*) jsCookiesString forURLString:(NSString*) urlString;
-(void) updateFromCookieStorage:(BSHTTPCookieStorage*) other;
@end
// ---
@interface NSHTTPCookie (BSHTTPCookieStorage) <NSCoding>
@end
// ---
//
// BSHTTPCookieStorage.m
//
// Created by Sasmito Adibowo on 02-07-12.
// Copyright (c) 2012 Basil Salad Software. All rights reserved.
// http://basilsalad.com
//
// Licensed under the BSD License <http://www.opensource.org/licenses/bsd-license>
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// this is ARC code.
#if !__has_feature(objc_arc)
#error Need automatic reference counting to compile this.
#endif
#import "FoundationAdditionsMacros.h"
#import "FoundationAdditions.h"
#import "BSHTTPCookieStorage.h"
@interface BSHTTPCookieStorage()
/*
Cookie storage is stored in the order of
domain -> path -> name
This one stores cookies that are subdomain specific
*/
@property (nonatomic,strong,readonly) NSMutableDictionary* subdomainCookies;
/*
Cookie storage is stored in the order of
domain -> path -> name
This one stores cookies global for a domain.
*/
@property (nonatomic,strong,readonly) NSMutableDictionary* domainGlobalCookies;
@end
@implementation BSHTTPCookieStorage
@synthesize subdomainCookies = _subdomainCookies;
@synthesize domainGlobalCookies = _domainGlobalCookies;
-(void) removeCookiesNamed:(NSString*) cookieName
{
void (^removeCookiesInStorage)(NSMutableDictionary*) = ^(NSMutableDictionary* domainStorage) {
[domainStorage enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id domain, NSMutableDictionary* pathStorage, BOOL *stop) {
[pathStorage enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id path, NSMutableDictionary* nameStorage, BOOL *stop) {
[nameStorage removeObjectForKey:cookieName];
}];
}];
};
removeCookiesInStorage(self.domainGlobalCookies);
removeCookiesInStorage(self.subdomainCookies);
}
- (void)setCookie:(NSHTTPCookie *)aCookie
{
// only domain names are case insensitive
NSString* domain = [[aCookie domain] lowercaseString];
NSString* path = [aCookie path];
NSString* name = [aCookie name];
NSMutableDictionary* domainStorage = [domain hasPrefix:@"."] ? self.domainGlobalCookies : self.subdomainCookies;
NSMutableDictionary* pathStorage = [domainStorage objectForKey:domain];
if (!pathStorage) {
pathStorage = [NSMutableDictionary new];
[domainStorage setObject:pathStorage forKey:domain];
}
NSMutableDictionary* nameStorage = [pathStorage objectForKey:path];
if (!nameStorage) {
nameStorage = [NSMutableDictionary new];
[pathStorage setObject:nameStorage forKey:path];
}
if ([[aCookie expiresDate] timeIntervalSinceNow] < 0) {
// delete cookie
[nameStorage removeObjectForKey:name];
} else {
[nameStorage setObject:aCookie forKey:name];
}
}
-(void) updateFromCookieStorage:(BSHTTPCookieStorage*) other
{
void(^updateCookies)(NSDictionary*) = ^(NSDictionary* domainStorage) {
[domainStorage enumerateKeysAndObjectsUsingBlock:^(NSString* domain, NSDictionary* pathStorage, BOOL *stop) {
[pathStorage enumerateKeysAndObjectsUsingBlock:^(NSString* path, NSDictionary* nameStorage, BOOL *stop) {
[nameStorage enumerateKeysAndObjectsUsingBlock:^(NSString* name, NSHTTPCookie* cookie, BOOL *stop) {
[self setCookie:cookie];
}];
}];
}];
};
updateCookies(other.domainGlobalCookies);
updateCookies(other.subdomainCookies);
}
- (NSArray *)cookiesForURL:(NSURL *)theURL
{
NSMutableDictionary* resultCookies = [NSMutableDictionary new];
NSString* cookiePath = [theURL path];
void (^cookieFinder)(NSString*,NSDictionary*) = ^(NSString* domainKey,NSDictionary* domainStorage) {
NSMutableDictionary* pathStorage = [domainStorage objectForKey:domainKey];
if (!pathStorage) {
return;
}
for (NSString* path in pathStorage) {
if ([path isEqualToString:@"/"] || [cookiePath hasPrefix:path]) {
NSMutableDictionary* nameStorage = [pathStorage objectForKey:path];
[resultCookies addEntriesFromDictionary:nameStorage];
}
}
};
NSString* cookieDomain = [[theURL host] lowercaseString];
// find domain-global cookies
NSRange range = [cookieDomain rangeOfString:@"."];
if (range.location != NSNotFound) {
NSString* globalDomain = [cookieDomain substringFromIndex:range.location];
cookieFinder(globalDomain,self.domainGlobalCookies);
}
// subdomain cookies will override the domain-global ones.
cookieFinder(cookieDomain,self.subdomainCookies);
return [resultCookies allValues];
}
-(void) loadCookies:(id<NSFastEnumeration>) cookies
{
for (NSHTTPCookie* cookie in cookies) {
[self setCookie:cookie];
}
}
-(void) handleCookiesInRequest:(NSMutableURLRequest*) request
{
NSURL* url = request.URL;
NSArray* cookies = [self cookiesForURL:url];
NSDictionary* headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
NSUInteger count = [headers count];
__unsafe_unretained id keys[count], values[count];
[headers getObjects:values andKeys:keys];
for (NSUInteger i=0;i<count;i++) {
[request setValue:values[i] forHTTPHeaderField:keys[i]];
}
}
-(void) handleCookiesInResponse:(NSHTTPURLResponse*) response
{
NSURL* url = response.URL;
NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:response.allHeaderFields forURL:url];
[self loadCookies:cookies];
}
-(void) handleWebScriptCookies:(NSString*) jsCookiesString forURLString:(NSString*) urlString
{
if (![jsCookiesString isKindOfClass:[NSString class]]) {
DebugLog(@"Not a valid cookie string: %@",jsCookiesString);
return;
}
if (![urlString isKindOfClass:[NSString class]]) {
DebugLog(@"Not a URL string: %@",urlString);
return;
}
if ([@"undefined" isEqualToString:jsCookiesString] || [@"null" isEqualToString:jsCookiesString]) {
DebugLog(@"Invalid cookie string");
return;
}
NSURL* jsUrl = [NSURL URLWithString:urlString];
if (!jsUrl) {
DebugLog(@"Malformed URL String: %@",urlString);
return;
}
NSString* const urlDomain = [jsUrl host];
if (!urlDomain) {
DebugLog(@"No domain in URL %@ - ignoring.",urlDomain);
return;
}
NSArray* cookiesArray = [jsCookiesString componentsSeparatedByString:@";"];
NSMutableDictionary* cookiesDict = [NSMutableDictionary dictionaryWithCapacity:cookiesArray.count];
NSCharacterSet* whitespace = [NSCharacterSet whitespaceCharacterSet];
for (NSString* cookiePair in cookiesArray) {
NSArray* pair = [cookiePair componentsSeparatedByString:@"="];
if (pair.count == 2) {
NSString* key = [pair[0] stringByTrimmingCharactersInSet:whitespace];
NSString* value = [pair[1] stringByTrimmingCharactersInSet:whitespace];
if (key.length > 0 && value.length > 0) {
// don't decode the cookie values
cookiesDict[key] = value;
}
}
}
// five years expiry for JavaScript cookies.
// why five years? Because it looks like Yammer's JavaScript-based cookies usually expires in five years.
NSDate* const cookieExpiryDate = [NSDate dateWithTimeIntervalSinceNow:5 * 365.25f * 24 * 3600];
// we got all JavaScript's cookie name/value pairs in 'cokiesDict' Now find existing cookies and override their values.
NSArray* existingCookies = [self cookiesForURL:jsUrl];
for (NSHTTPCookie* existingCookie in existingCookies) {
NSString* existingName = existingCookie.name;
NSString* updatedValue = cookiesDict[existingName];
if (updatedValue) {
if (![updatedValue isEqualToString:existingCookie.value]) {
// override
NSMutableDictionary* cookieProperties = [NSMutableDictionary dictionaryWithDictionary:existingCookie.properties];
cookieProperties[NSHTTPCookieValue] = updatedValue;
cookieProperties[NSHTTPCookieExpires] = cookieExpiryDate;
NSHTTPCookie* updatedCookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
[self setCookie:updatedCookie];
}
// we already found an existing one, don't add it as domain global cookie
[cookiesDict removeObjectForKey:existingName];
}
}
// now set the rest as domain-global cookies
if (cookiesDict.count > 0) {
NSString* cookieDomain = urlDomain;
NSArray* domainComponents = [urlDomain componentsSeparatedByString:@"."];
if (domainComponents.count > 2) {
NSMutableString* makeDomain = [NSMutableString stringWithCapacity:cookieDomain.length];
[domainComponents enumerateObjectsUsingBlock:^(NSString* component, NSUInteger idx, BOOL *stop) {
if (idx == 0) {
return; // skip the first one
}
[makeDomain appendFormat:@".%@",component];
}];
cookieDomain = makeDomain;
}
NSString* const cookiePath = @"/";
[cookiesDict enumerateKeysAndObjectsUsingBlock:^(NSString* cookieName, NSString* cookieValue, BOOL *stop) {
NSMutableDictionary* cookieParams = [NSMutableDictionary dictionaryWithCapacity:6];
cookieParams[NSHTTPCookieDomain] = cookieDomain;
cookieParams[NSHTTPCookiePath] = cookiePath;
cookieParams[NSHTTPCookieExpires] = cookieExpiryDate;
cookieParams[NSHTTPCookieName] = cookieName;
cookieParams[NSHTTPCookieValue] = cookieValue;
NSHTTPCookie* cookie = [NSHTTPCookie cookieWithProperties:cookieParams];
[self setCookie:cookie];
}];
}
}
#pragma mark Property Access
-(NSMutableDictionary *)subdomainCookies
{
if (!_subdomainCookies) {
_subdomainCookies = [NSMutableDictionary new];
}
return _subdomainCookies;
}
-(NSMutableDictionary *)domainGlobalCookies
{
if (!_domainGlobalCookies) {
_domainGlobalCookies = [NSMutableDictionary new];
}
return _domainGlobalCookies;
}
-(void)reset
{
[self.subdomainCookies removeAllObjects];
[self.domainGlobalCookies removeAllObjects];
}
#pragma mark NSCoding
-(id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [self init]) {
_domainGlobalCookies = [aDecoder decodeObjectForKey:@"domainGlobalCookies"];
_subdomainCookies = [aDecoder decodeObjectForKey:@"subdomainCookies"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
if (_domainGlobalCookies) {
[aCoder encodeObject:_domainGlobalCookies forKey:@"domainGlobalCookies"];
}
if (_subdomainCookies) {
[aCoder encodeObject:_subdomainCookies forKey:@"subdomainCookies"];
}
}
#pragma mark NSCopying
-(id)copyWithZone:(NSZone *)zone
{
BSHTTPCookieStorage* copy = [[[self class] allocWithZone:zone] init];
if (copy) {
copy->_subdomainCookies = [self.subdomainCookies deepMutableCopy];
copy->_domainGlobalCookies = [self.domainGlobalCookies deepMutableCopy];
}
return copy;
}
@end
// ---
@implementation NSHTTPCookie (BSHTTPCookieStorage)
-(id)initWithCoder:(NSCoder *)aDecoder
{
NSDictionary* cookieProperties = [aDecoder decodeObjectForKey:@"cookieProperties"];
if (![cookieProperties isKindOfClass:[NSDictionary class]]) {
// cookies are always immutable, so there's no point to return anything here if its properties cannot be found.
return nil;
}
self = [self initWithProperties:cookieProperties];
return self;
}
-(void) encodeWithCoder:(NSCoder *)aCoder
{
NSDictionary* cookieProperties = self.properties;
if (cookieProperties) {
[aCoder encodeObject:cookieProperties forKey:@"cookieProperties"];
}
}
@end
// ---
@murthy8
Copy link

murthy8 commented Mar 30, 2016

@jjconti Thanks for giving nice tutorial.. actually i have a some doubt. i am creating webview.. in webview i have some of cookies are there... so i want to delete only some cookies... remaining cookies i dont want to delete. so in this scenario i need to findout cookies file name.. based on cookies file name i can be able to delete some cookies... so how can i found out cookies file name.. will you guide me please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment