ページ

2009年8月20日木曜日

NSHTTPCookieStorage.m のソースコードを読む

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

前回の続き。今回は NSHTTPCookieStorage.m のソースコードを読む。

ソースコードは Apple提供のものではなく mySTEP(QuantumSTEP)提供のヤツ。

NSHTTPCookieStorage.h

NSHTTPCookieStorage.m


まずはヘッダファイルから。

NSHTTPCookieStorage.h

#import 

@class NSMutableArray, NSMutableDictionary, NSDistributedLock, NSDate, NSTimer;

typedef enum _NSHTTPCookieAcceptPolicy
{
NSHTTPCookieAcceptPolicyAlways=0,
:
} NSHTTPCookieAcceptPolicy;
:
@interface NSHTTPCookieStorage : NSObject
{
NSMutableDictionary *_cookies;
NSDistributedLock *_lock;
NSDate *_lastModification;
NSHTTPCookieAcceptPolicy _cookieAcceptPolicy;
NSTimer *_timer;
BOOL _touched;
}
: 以下、メソッド定義が続く
:


メンバ変数は名前からおおよその用途がわかる。_lock はファイル書き出しで使うのだろう。_lastModification はクッキー情報全体の最終更新日時で、書き出し時の判断で使うのだろうか。NSTimer も使われているが、非同期書き出し(定期書き出し)にでも使うのかもしれない。_touched は変更の有無か。


次は NSHTTPCookieStorage.m。上から読んでいこう。


#define COOKIE_DATABASE @"~/Library/Cookies/Cookies.plist" // Array of Dictionaries
#define COOKIE_DATABASE_LOCK COOKIE_DATABASE@".lock"

まず定数定義。べた書きで Cookies.plist と ロックファイルの名前が定義されている。


クラスメソッド。一旦作成したインスタンスを使い回すシングルトン。
+ (NSHTTPCookieStorage *) sharedHTTPCookieStorage
{
static NSHTTPCookieStorage *_shared;
if(!_shared)
_shared=[[self alloc] init];
return _shared;
}



次は _sync。これはメモリ内のデータをファイルへ書き出す処理を司っているようだ。
- (void)_sync

中身を見る前に他のメソッドでの使われ方を先にみることにしよう。
後回しにして次へ行く。


次は全クッキー情報の取得。初回存在しない場合は _sync を呼び出している。多分、ファイルから読み出すのだろう。
- (NSArray *) cookies;
{
if(!_cookies)
[self _sync];
return [_cookies allValues];
}



続いては指定 URLにマッチするクッキーの取得。
- (NSArray *) cookiesForURL:(NSURL *) url

長いのでソースはオリジナルを見てもらうとして、ポイントだけ解説。

クッキーを1件づつ処理する。
while((cookie=[c nextObject]))
:
:
(1) if([domain hasPrefix:@"."])
{
(1') if(![[url host] hasSuffix:domain])
continue; // does not match suffix
}
(2) else if(![[url host] isEqualToString:domain])
continue; // does not match exact domain
path=[cookie path];
(3) if(![path isEqualToString:@"/"] && ![[url path] isEqualToString:path])
continue; // neither "all paths" nor specific path
(4) if([cookie isSecure] && ![[url scheme] hasSuffix:@"s"])
continue; // not requested by secure protocol
(5) if((portList=[cookie portList]) && ![portList containsObject:[url port]])
continue; // no match with port list
[r addObject:cookie]; // exact match
}
return r;


(1) ドメイン名チェック
クッキーのドメイン名が .(ドット)で始まる場合、ホスト名のサフィックスチェックを行う。

URL:host=www.some.site.com
Cookie:domain=.site.com ならOK(次のチェックへ)

(2) ホスト名チェック
URLのホスト名とクッキーのドメイン名が一致するかをチェックする。

URL:host=www.some.site.com
Cookie:domain=www.some.site.com ならOK(次のチェックへ)

(3) パスチェック
URLのパスとクッキーのパスが一致するかをチェックする。
クッキーのパスが /(ルート)ならこのチェックはパスして次へ。

URL:path=/foo/bar
Cookie:path=/foo/bar ならOK(次のチェックへ)

#ん?Cookieの仕様上 Cookie:path=/foo は駄目だったか?

(4) セキュアチェック
クッキーがセキュア指定されている場合は HTTPSの時のみクッキー送出を許す。
コードでは [url scheme] の最後が s で終わっているかどうかでチェックしている。

(5) ポートチェック
クッキーにポート指定がある場合は、URLのポートがそれに一致するかチェックする。
#ポートチェックが必要なのは知らなかった。

(1)〜(5)すべてのチェックがパスできたら該当するクッキーを配列へ加え、最後にそれを返す。



次は削除。domainとpath、nameを連結した文字列をキーとして該当するクッキーを削除している。なるほど domain+path+nameをキーとしているのか。
- (void) deleteCookie:(NSHTTPCookie *) cookie
{
[_cookies removeObjectForKey:[NSString stringWithFormat:@"%@/%@/%@", [cookie domain], [cookie path], [cookie name]]];
before we did sync?
_touched=YES;
}



次は登録。プライバシーポリシーをチェックしたのち、domain+path+name をキーにして格納する。初回は _cookies がnilなので _syncでファイルから読み込む。最後に _touchedを立てておく。
- (void) setCookie:(NSHTTPCookie *) cookie
{
NSString *key;
if(_cookieAcceptPolicy == NSHTTPCookieAcceptPolicyNever)
return;
key=[NSString stringWithFormat:@"%@/%@/%@", [cookie domain], [cookie path], [cookie name]];
if(!_cookies)
[self _sync];
[_cookies setObject:cookie forKey:key];
_touched=YES;
}


次は配列で登録。配列をループにして setCookie:を繰り返し呼び出す。よく見ると mainURL が使われていない。
- (void) setCookies:(NSArray *) cookies forURL:(NSURL *) url mainDocumentURL:(NSURL *) mainURL
{
NSEnumerator *c;
NSHTTPCookie *cookie;
if(_cookieAcceptPolicy == NSHTTPCookieAcceptPolicyNever)
return;
c=[_cookies objectEnumerator];
while((cookie=[c nextObject]))
{
// [cookie setMainDocumentURL:mainURL]
[self setCookie:cookie];
}
}



ポリシーのゲッター。
- (NSHTTPCookieAcceptPolicy) cookieAcceptPolicy
 { return _cookieAcceptPolicy; }


ポリシーのセッター。_touchedを立てている。
- (void) setCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy) policy
{
if(_cookieAcceptPolicy != policy)
{
_cookieAcceptPolicy=policy;
_touched=YES;
}
}



イニシャライザ。NSDistributedLock でロックを用意した後、30秒毎に _sync を呼び出すタイマーを設定している。なるほど非同期で定期書き出しを行っているのか。
- (id) init
{
if((self=[super init]))
{
_lock=[[NSDistributedLock alloc] initWithPath:COOKIE_DATABASE_LOCK];
_timer=[[NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(_sync) userInfo:nil repeats:YES] retain];
}
return self;
}


最後に dealloc。
- (void) dealloc
{
[_timer invalidate];
[_timer release];
if(_touched)
[self _sync]; // final sync
[_lock release];
[_cookies release];
[_lastModification release];
[super dealloc];
}



さて飛ばした _sync を見てみる。これまでの状況から、(1)初回ファイルからの読み込み (2)30秒毎のファイル書き出し、の2つの処理を行っているようだ。

こちらも長いのでポイントだけ。
- (void) _sync

最初にロック獲得を試みる。ロックがとれない間は 1秒スリープを繰り返す。むう。タイムアウト(もしくは回数制限)が無いので下手すると延々とここにとどまるのだろうか。
 while(![_lock tryLock])
sleep(1);



次にクッキーをファイルから読み込むかどうかの判断。
 if(!_cookies || [[[[NSFileManager defaultManager] fileAttributesAtPath:COOKIE_DATABASE traverseLink:YES] fileModificationDate] compare:_lastModification] == NSOrderedDescending)

初回(_cookies==nil)もしくは、インスタンス変数で保持している最終更新日時よりもファイルの更新日時が新しい場合に、これ以降の処理(ファイル読み込み)を行う。
後者のチェックは複数のアプリでこのインスタンスが使われることを想定したものだろう。

ファイルの読み込み処理。クラスメソッド一発。エラーが起きたらどうなるのやら。。
NSArray *a=[NSArray arrayWithContentsOfFile:COOKIE_DATABASE]; // read all properties


読み込んだ内容を NSHTTPCookie のインスタンスへ詰め替える。
NSEnumerator *e=[a objectEnumerator];
NSDictionary *props;
if(!_cookies)
_cookies=[[NSMutableDictionary alloc] initWithCapacity:100];
while((props=[e nextObject]))
{ // merge changes
NSString *key=[NSString stringWithFormat:@"%@/%@/%@", [props objectForKey:NSHTTPCookieDomain], [props objectForKey:NSHTTPCookiePath], [props objectForKey:NSHTTPCookieName]];
NSHTTPCookie *cookie=[_cookies objectForKey:key];
:
:

作成した NSHTTPCookie は domain+path+name をキーにして NSMutableDictionary へ格納していく。


続いて書き出し。
 if(_touched || !_lastModification)

最初に変更があった(_touched==YES)かとうかをチェックする。また1度も書き出しが行われていない(_lastModification==nil)場合も書き出すことにする。

書き出し処理。
    while((cookie=[e nextObject]))
[a addObject:[cookie properties]]; // just save the properties
[a writeToFile:COOKIE_DATABASE atomically:YES]; // should we write a binary property list???
_lastModification=[[[[NSFileManager defaultManager] fileAttributesAtPath:COOKIE_DATABASE traverseLink:YES] fileModificationDate] retain]; // modified
}



書き出し後ににロックを解除する。
 [_lock unlock];


最後に変更があった場合は NSHTTPCookieStorageCookiesChangedNotification を投げる。
 if(changed)
[[NSNotificationCenter defaultCenter] postNotificationName:NSHTTPCookieStorageCookiesChangedNotification object:self];


#って、コードを見ると changed を設定してるところが無いぞ。

- - - -
怪しいところがいくつもありこのままでは使えないな。だが参考になった。