WPSU (WebPage Screenshot Utility) の開発に戻る。先日までに開発した XCHTTPCookieStorage を組み込む。
ソースコードをコピーし、webView:resource:willSendRequest:redirectResponse:fromDataSource:dataSource 等のデリゲートを実装するだけ。
実行すると無事にログインできた(クッキーが使えた)。
クッキーのファイル((plist)もちゃんと書き出されている。
これで Safari などと独立してクッキーを管理できる様になった。
ソースコード:WebPageScreenshotUtility-03.zip
2009年9月17日木曜日
WPSU(15) - XCHTTPCookieStorage組み込み
2009年9月16日水曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (14) 仕様まとめと最新コード
クッキーの扱いに関するルールをまとめておく。
(XCHTTPCookieStorage の仕様に相当する)
1. 基本ルール
・クッキーは、ドメイン、パス、名前の3つの組み合わせをキーとして扱う。
・クッキーを保存する場合、同一のキーが存在する場合は上書きする。
2. ドメインのルール
(1) 受け入れルール
・gTLD (global Top Level Domain) のみは受け入れる。
・国毎に規定される Effective TLD に該当するものは受け入れる。
(リンク:Effective TLD Service)
プログラムでは effective_tld_names.dat を使用している。
・上記に加え、NSHTTPCookieAcceptPolicy に従う。
NSHTTPCookieAcceptPolicyAlways:無条件にクッキーを受け入れる。
NSHTTPCookieAcceptPolicyNever:無条件にクッキーを受け入れない。
NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain:
リクエストURLのドメインと後方一致する場合のみ受け入れる。
クッキードメイン:.xcatsan.com
リクエストURLホスト:
www.xcatsan.com => OK
jango.www.xcatsan.com => OK
xcatsan.com => OK(先頭に . が無い場合も OK)
(2) 送出ルール
・クッキーのドメインがリクエストURLのホスト名と後方一致する場合に送出する。
(OK)クッキードメイン:.xcatsan.com
リクエストURLホスト:www.xcatsan.com
・クッキーのドメインの先頭に . が付く場合、これを取り除いて一致する場合も送出する。
(OK)クッキードメイン:.www.xcatsan.com
リクエストURLホスト:www.xcatsan.com
3. パスのルール
(1) 送出ルール
・クッキーのパスがリクエストURLのパスと前方一致する場合に送出する。
クッキーパス:/a
リクエストURLパス:
/a/catsan.html => OK
/a/b/c/mikeneko.html => OK
/x/a/catsan.html => NG
/abc/catsan.html => NG
・同じドメイン内で同じ名前を持つクッキーが存在する場合は、より詳細な方を返す。
例)クッキーのパスが "/acme" と "/acme/ammo" で同じ nameの Cookieが存在する場合、
a) リクエストURLのパスが "/acme/ammo/test.html" の場合、後者を返す
b) リクエストURLのパスが "/acme/parts/some.html" の場合、前者を返す
4. その他のルール
(1) Secure 指定がある場合は、リクエストのURLスキームが https の場合のみ該当するクッキーを送出する。
(2) 有効期限が切れたクッキーは送出しない。またファイルへ書き出さない(既にファイル内に存在する場合は削除する)。
(3) SessionOnly 指定がある場合は、ファイルへ書き出さない。
* ドメインの受け取りルールの適用例
com => NG
.com => NG
xcatsan.com => OK
.xcatsan.com => OK
www.xcatsan.com => OK
jp => NG
.jp => NG
xcatsan.jp => OK
.xcatsan.jp =>OK
co.jp => NG
.co.jp => NG
xcatsan.co.jp =>OK
www.xcatsan.co.jp =>OK
tokyo.jp => NG
.tokyo.jp => NG
chiyoda.tokyo.jp =>NG
metro.tokyo.jp => OK
www.chiyoda.tokyo.jp =>OK
- - - -
さらにソースコードに若干修正をいれた(フォルダ作成のタイミング、ファイル読み込み時のポリシー非適用など)。
最新版を掲載しておく。
ソースコード:CookieStorage-7.zip
2009年9月15日火曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (13)ドメインチェックルール調整
GoogleAppsへログインできることが確認できたのだが、また一つ問題に気がついた。
ログインボックスに「ログイン状態を保持する」というのがある。
チェックを入れておくと、次回ブラウザでアクセスした時に自動的にログイン状態になる。Safariではそのような動作になっていた。
ところがサンプルアプリの CookieStorageではこれが動作しない。ログイン状態は恐らくクッキーで管理されていると思われるので、なんらかの理由で CookieStorage がクッキーが受け取れていないか、あるいは送出できていないと予想される。
Safari のクッキーを眺めているとこのクッキーはどうも "HID"という名前を持つものだと分かった。
リクエストのURLと、このクッキーのドメインを照らし合わせてみると次のようになっていた。
クッキードメイン:.www.google.com
リクエストURL:www.google.com
なるほど。このケースは送出OKなのか。今の XCHTTPCookieStorage では NGにしているのでこれでは確かにクッキーが送出されない。Safari が動作し、GoogleAppsがそのような動作を期待しているということはこれが仕様なのだろう。手を入れて対応できるようにしよう。
クッキー送出判断ロジックに手を加えた。
以前のコードはこう。
XCHTTPCookieStorage.m
int index = [domain_parts count]-1;
NSString* part = [domain_parts objectAtIndex:index];
NSString* check_domain = [NSString stringWithFormat:@".%@", part];
index--;
while (index >=0) {
part = [domain_parts objectAtIndex:index];
if (index) {
check_domain = [NSString stringWithFormat:@".%@%@", part, check_domain];
} else {
check_domain = [NSString stringWithFormat:@"%@%@", part, check_domain];
}
index--;
以前はわざわざ最後のチェックで頭に . をつけないようにしていた。例えば、リクエストURL が www.google.com の場合、送出するクッキーを探すときのドメインは次のようになっていた。
[index= 2] .com
[index= 1] .google.com
[index= 0] www.google.com
新コードはこうなった。
int index = [domain_parts count]-1;
NSString* part = [domain_parts objectAtIndex:index];
NSString* check_domain = [NSString stringWithFormat:@".%@", part];
index--;
while (index >=-1) {
if (index >= 0) {
part = [domain_parts objectAtIndex:index];
check_domain = [NSString stringWithFormat:@".%@%@", part, check_domain];
} else {
check_domain = [check_domain substringFromIndex:1];
}
index--;
クッキーを探すときに頭に . が付くケースを追加した。
[index= 2] .com
[index= 1] .google.com
[index= 0] .www.google.com
[index=-1] www.google.com
加えて -[setCookies:forURL:mainDocumentURL:] 内の NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain チェックも頭に . が付くケースを追加した。
- (void)setCookies:(NSArray *)cookies forURL:(NSURL *)URL mainDocumentURL:(NSURL *)mainDocumentURL
{
:
(旧)NSString* url_host = [mainDocumentURL host];
(新)NSString* url_host = [NSString stringWithFormat:@".%@", [mainDocumentURL host]];
for (NSHTTPCookie *cookie in cookies) {
if (_cookie_accept_policy ==
NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain &&
![url_host hasSuffix:[cookie domain]]) {
continue;
}
[self setCookie:cookie];
}
}
実行すると GoogleApps で無事にログイン状態が保持された。
ソースコード:CookieStorage-6.zip
2009年9月14日月曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (12)バグ修正他
触っていると GoogleApps でのログインに失敗することが判明。
原因はパスチェックにあった。
今までは比較対象のクッキーのパスへ無条件に / をつけていた。
if (![url_path isEqualToString:cookie_path]) {
if (![url_path hasPrefix:[cookie_path stringByAppendingString:@"/"]]) {
continue;
}
}この場合
url_path: /a/some.com/sample.gif
cookie_path: /a/some.com
は問題ないのだが
url_path: /a/some.com/LoginAction
cookie_path: /a/some.com/ ※最後に / がつく
のケースはcookie_pathの末尾に / を重ねてしまうので、クッキー送出の対象にならなくなっていた。
そこで末尾についているケースをきちんと対処してやる。
if (![url_path isEqualToString:cookie_path]) {
if (![cookie_path hasPrefix:@"/"]) {
cookie_path = [cookie_path stringByAppendingString:@"/"];
}
if (![url_path hasPrefix:cookie_path]) {
continue;
}
}
そもそも最後に / をつけていたのは単純な後方一致だと
url_path: /a/some.com.gif
cookie_path: /a/som.com
のようなケースも拾ってしまうため。
末尾に / をつけてチェック( cookie_path: /a/some.com/ )することでこういった誤送信を防げる。
修正後、無事に GoogleAppsへログインできた。
#####
なおバグとは別に、クッキーのクリアができた方が便利なので NSHTTPCookieStorage には無い、オリジナルのメソッドを追加しておく。
- (void)clearCookies
{
for (NSMutableDictionary* cookies2 in [_cookies allValues]) {
[cookies2 removeAllObjects];
}
[_cookies removeAllObjects];
_is_modified = YES;
[self _writeCookies:nil];
}
呼び出すと、メモリとファイル両方のクッキーを削除する(ファイル自体は空のまま残す)。
2009年9月11日金曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (11)完成〜クッキーの保存/読み取りを実装
NSHTTPCookieStorage 互換クラスの自前実装の最後。残っていたクッキーのファイルへの保存と読み取りの実装に取りかかる。
まずは書き出しから。plist 形式でクッキーの内容を保存する。
XCHTTPCookieStorage.m
- (void)_writeCookies:(NSTimer*)timer
{
if (!_is_modified) {
return;
}
NSMutableArray* write_cookies = [NSMutableArray array];
for (NSHTTPCookie* cookie in [self cookies]) {
if ([self _isWritableCookie:cookie]) {
[write_cookies addObject:[cookie properties]];
}
}
BOOL result = [write_cookies writeToFile:[self _filepath] atomically:YES];
if (result) {
_is_modified = NO;
} else {
NSLog(@"Failed to write : %@", [self _filepath]);
}
}
_is_modified はメモリ上のクッキーに追加や変更が入った場合に YESになる。-[cookies] でメモリ上に保持しているクッキーを取得し保存対象かどうかを -[_isWritableCookie:] でチェックし、最後に -[NSArray writeToFile:atomically:] で保存する。atomically:YES とすると一旦テンポラリファイルへ書き出した後、目的のファイルへコピー(上書き)する。これは Safari のクッキー調査で見た動作と同じ。
なお単一アプリからの利用しか想定していないので排他制御(ロック)は行っていない。NSTimerからの呼出しも同一スレッドなのでアプリ内でのファイル書き出し競合も通常は起らない。
保存対象かどうかの判断を行うメソッド。
- (BOOL)_isWritableCookie:(NSHTTPCookie*)cookie
{
if ([cookie isSessionOnly]) {
return NO;
}
NSDate* cookie_expires_date = [cookie expiresDate];
NSDate* date = [NSDate date];
if (cookie_expires_date &&
[date compare:cookie_expires_date] == NSOrderedDescending) {
return NO;
}
return YES;
}
セッション限りのもの、期限切れのものは保存しない。
クッキーファイルの保存場所を決定するメソッド。
- (NSString*)_filepath
{
NSString* path = [NSString stringWithFormat:@"%@/%@", [NSSearchPathForDirectoriesInDomains(NSAllLibrariesDirectory, NSUserDomainMask, YES) objectAtIndex:0], @"Cookies"];
NSError* error;
NSFileManager* fm = [NSFileManager defaultManager];
if (![fm isReadableFileAtPath:path]) {
[fm createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:nil
error:&error
];
if (error) {
NSLog(@"%@", error);
}
}
NSString* identifier = [[NSBundle mainBundle] bundleIdentifier];
NSString* filepath =
[NSString stringWithFormat:@"%@/%@.plist", path, identifier];
return filepath;
}
保存先は ~/Library/Cookies、ファイル名はアプリケーションの Identifier + ".plist" とする。保存先のフォルダが存在しない場合は作成する。
最後に -[_writeCookies:] を NSTimerに登録し、30秒毎に(非同期で)保存する。
#define SYNC_INTERVAL 30
- (id)init
{
if (self = [super init]) {
:
_timer=[[NSTimer scheduledTimerWithTimeInterval:SYNC_INTERVAL
target:self
selector:@selector(_writeCookies:)
userInfo:nil repeats:YES] retain];
:
}
これで書き出しの実装ができた。
次は読み込み。これは既存のメソッドの組み合わせで簡単にできる。
- (void)_readCookies
{
NSArray* read_cookies =
[NSArray arrayWithContentsOfFile:[self _filepath]];
if (read_cookies) {
for (NSDictionary* properties in read_cookies) {
[self setCookie:[NSHTTPCookie cookieWithProperties:properties]];
}
}
}
さて実行してみよう。
クッキーを送出するサイトを開き少し(30秒)待つとファイルが生成された。

中身をみるとクッキーが書き出されているようだ。

試しにログイン状態でアプリケーションを一旦終了し、再起動してみる。

ページを開くとログイン状態が保たれていた。ファイルへ保存したクッキーがメモリへ読み込まれきちんと送出されている。
最後にアプリ終了時にクッキーを保存するため NSApplicationWillTerminateNotification の受取を登録しておく。
- (id)init
{
if (self = [super init]) {
:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_willTerminate:)
name:NSApplicationWillTerminateNotification
object:nil];
:
}
アプリ終了直前に NSApplicationWillTerminateNotification がポストされたら下記のメソッドを呼出してクッキーを保存する。
- (void)_willTerminate:(NSNotification*)notification
{
[self _writeCookies:nil];
}
ソースコード:CookieStorage-5.zip
- - - -
ようやくクッキーのハンドリングが一段落できた(長かった)。
2009年9月10日木曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (10)クッキー受取ポリシーチェックの実装
永続化の前に NSHTTPCookieAcceptPolicy への対応を行っておく。
ヘッダファイル NSHTTPCookieStorage.h によれば3種類の値が存在する。
NSHTTPCookieStorage.h
/*!
@enum NSHTTPCookieAcceptPolicy
@abstract Values for the different cookie accept policies
@constant NSHTTPCookieAcceptPolicyAlways Accept all cookies
@constant NSHTTPCookieAcceptPolicyNever Reject all cookies
@constant NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain Accept cookies
only from the main document domain
*/
enum {
NSHTTPCookieAcceptPolicyAlways,
NSHTTPCookieAcceptPolicyNever,
NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain
};
typedef NSUInteger NSHTTPCookieAcceptPolicy;
Safari(4.0)のセキュリティ設定を見ると3選択があるのでこれらが対応しているのだろう。

ポリシー適用には URLが必要になるので setCookies:forURL:mainDocumentURL: でチェックを行うことにする。前回のコードにポリシー適用の条件を加えた。
XCHTTPCookieStorage.m
- (void)setCookies:(NSArray *)cookies forURL:(NSURL *)URL mainDocumentURL:(NSURL *)mainDocumentURL
{
if (_cookie_accept_policy == NSHTTPCookieAcceptPolicyNever) {
return;
}
NSString* url_host = [mainDocumentURL host];
for (NSHTTPCookie *cookie in cookies) {
if (_cookie_accept_policy ==
NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain &&
![url_host hasSuffix:[cookie domain]]) {
continue;
}
[self setCookie:cookie];
}
}
NSHTTPCookieAcceptPolicyNever は無条件に拒否、NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain の場合はドメインチェックを行うようにした。
また setCookie: にも NSHTTPCookieAcceptPolicyNever の場合の拒否ロジックを加えておいた。
デフォルト値は Safariに合わせて NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain にしておいた。
2009年9月8日火曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (9)クッキー受取のドメインチェック強化
XCEffectiveTLDNames を使いドメインチェックを強化する。
といっても3行を加えるだけ。
NSHTTPCookieStorage.m
- (void)setCookie:(NSHTTPCookie *)cookie
{
if ([[XCEffectiveTLDNames sharedEffectiveTLDNames] isEffectiveTLDName:[cookie domain]]) {
return;
}
:
また、クッキー送出もとのサーバのドメインがクッキーで設定されているドメインと一致するかをチェックする。これはリクエストURLが必要なので -[setCookies:forURL:mainDocumentURL:] へ書いてみた。
- (void)setCookies:(NSArray *)cookies forURL:(NSURL *)URL mainDocumentURL:(NSURL *)mainDocumentURL
{
NSString* url_host = [URL host];
for (NSHTTPCookie *cookie in cookies) {
if ([url_host hasSuffix:[cookie domain]]) {
[self setCookie:cookie];
}
}
}
クッキーのドメインがリクエストURLホストの後方一致となるかをチェックする。
(例)リクエストURLのホスト:www.xcatsan.com
a) クッキードメインが .xcatsan.com の場合:OK
b) クッキードメインが www.xcatsan.com の場合:OK
c) クッキードメインが .mail.xcatsan.com の場合:NG
動作確認は、Googleのログインで成功した。大丈夫そうだ。
サンプル:CookieStorage-4.zip
- - - -
クッキー受取、送出が大体実装できた。残りは永続化の部分だな。
2009年9月7日月曜日
NSHTTPCookieのドメインに . が付く、付かない?
NSHTTPCookie の挙動で一つ気がついたことがある。
domain="www.xcatsan.com" のクッキーをサーバから送り、それを NSHTTPCookie へ渡すと domain=".www.xcatsan.com" となる( . ドットが頭につく)。
<NSHTTPCookie version:0 name:@"testname" value:@"testvalue"
expiresDate:@"(null)" created:@"273711615.645900" sessionOnly:TRUE
domain:@".www.xcatsan.com" path:@"/system" secure:FALSE
comment:@"(null)" commentURL:@"(null)" portList:(null)>,
そういう仕様かと思いきや他のサイトのクッキーを眺めていると頭に . が付かないものがあった。
<NSHTTPCookie version:0 name:@"GoogleAccountsLocale_session" value:@"ja"
expiresDate:@"(null)" created:@"273711677.501391" sessionOnly:TRUE
domain:@"www.google.co.jp" path:@"/accounts" secure:FALSE
comment:@"(null)" commentURL:@"(null)" portList:(null)>,
この差はなんだろうか。気になって調べてみた。
:
:
原因は単純でサーバ側がクッキーをセットする時に domain指定しているか、していないかで . の有無が決まる。
- Set-Cookie: domain指定なし => 頭に.が付かない(例)"www.xcatsan.com"
- Set-Cookie: domain指定あり => 頭に.が付く(例)".www.xcatsan.com"
分かってみれば単純だった。前者は送出元のサーバに限定されたクッキー、後者は指定ドメインに一致するクッキーとなる。
2009年9月6日日曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (8)クッキー受取のロジックを改良する
Mozzila が提供する effective_tld_names.dat を使う実装を行う。
effective_tld_names.dat を扱う専用のクラス "XCEffectiveTLDnames" を用意する。
XCEffectiveTLDnames.h
@interface XCEffectiveTLDNames : NSObject {
NSMutableSet* _domain_set;
NSMutableSet* _wildcard_set;
NSMutableSet* _exception_set;
BOOL _is_loaded_file;
}
+ (XCEffectiveTLDNames*)sharedEffectiveTLDNames;
- (BOOL)isEffectiveTLDName:(NSString*)domain;
@end- _domain_set は、jp や co.jp などを格納する。
- _wildcard_set は、ワイルドカード指定されているドメイン(*.tokyo.jp など)を格納する(※実際に格納するのは "tokyo.jp")
- _exception_set は、! で始まる除外指定のドメイン(!metro.tokyo.jpなど)を格納する(※実際に格納するのは "metro.tokyo.jp")
ファイルはダウンロードして XCodeプロジェクト内の Resources へ入れておく。エンコーディングは仕様により UTF-8と規定されいている。

XCEffectiveTLDNames はシングルトンとする。
XCEffectiveTLDNames.m
+ (XCEffectiveTLDNames*)sharedEffectiveTLDNames
{
static XCEffectiveTLDNames* _shared = nil;
if (!_shared) {
_shared = [[XCEffectiveTLDNames alloc] init];
[_shared _loadFile];
}
return _shared;
}
初期化時にファイルの読み込みを行う。
- (void)_loadFile
{
NSError* error;
NSString* path = [NSString stringWithFormat:@"%@/%@",
[[NSBundle mainBundle] resourcePath], FILENAME];
NSString* contents = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
NSLog(@"%@", error);
_is_loaded_file = NO;
return;
}
for (NSString* line in [contents componentsSeparatedByString:@"\n"]) {
if ([line length] == 0) {
continue;
}
if ([line hasPrefix:@"//"]) {
continue;
}
if ([line hasPrefix:@"*."]) {
if ([line length] > 2) {
[_wildcard_set addObject:[line substringFromIndex:2]];
// *.tokyo.jp => tokyo.jp
}
continue;
}
if ([line hasPrefix:@"!"]) {
if ([line length] > 1) {
[_exception_set addObject:[line substringFromIndex:1]];
// !metoro.tokyo.jp => metoro.tokyo.jp
}
continue;
}
[_domain_set addObject:line];
}
_is_loaded_file = YES;
}
ちょっと大味な実装な気もするが Cocoaでファイル読み込みを扱ってみるとこんな感じになった。コメントや空行を取り除き、ワイルドカード、除外、ドメインをそれぞれ適切な NSMutableSet へ格納する。
これらを使い、クッキーで受け取れるドメインのチェックを行う。
- (BOOL)isEffectiveTLDName:(NSString*)domain
{
if ([domain hasPrefix:@"."] && [domain length] > 1) {
domain = [domain substringFromIndex:1];
}
if ([_domain_set containsObject:domain]) {
return YES;
}
if ([_exception_set containsObject:domain]) {
return NO;
}
if ([_wildcard_set containsObject:domain]) {
return YES;
}
NSRange range = [domain rangeOfString:@"."];
if (range.location == NSNotFound || (range.location+1) >= [domain length]) {
return NO;
}
NSString* check_domain = [domain substringFromIndex:range.location+1];
if ([_wildcard_set containsObject:check_domain]) {
return YES;
}
return NO;
}
最初に _domain_set, _exception_set, _wildcar_set に一致するかどうかチェックを行う。その後、ワイルドカード用にチェックを行う。Effective TLD の場合に YES を返す。実際にクッキーを受け取るかどうかはこの戻り値が NO の場合となる。
テストコードを書いて動作を確認してみよう。
- (void)_checkself
{
NSString* domain;
domain = @"com";
NSLog(@"%@: %d", domain, [self isEffectiveTLDName:domain]);
domain = @".com";
NSLog(@"%@: %d", domain, [self isEffectiveTLDName:domain]);
:
:
結果はこう(1=YES, 0=NO)。
com: 1
.com: 1
xcatsan.com: 0
.xcatsan.com: 0
www.xcatsan.com: 0
jp: 1
.jp: 1
xcatsan.jp: 0
.xcatsan.jp: 0
co.jp: 1
.co.jp: 1
xcatsan.co.jp: 0
www.xcatsan.co.jp: 0
tokyo.jp: 1
.tokyo.jp: 1
chiyoda.tokyo.jp: 1
metro.tokyo.jp: 0
www.chiyoda.tokyo.jp: 0
TLDはもちろん、ワイルドカードと除外指定もよさそうだ。
このクラスを XCHTTPCookieStorage で使おう。
なお何らかの理由でファイルの読み込みに失敗した場合は -[XCEffectiveTLDNames isEffectiveTLDName] の戻り値は常に NO が帰るのでセキュリティレベルが低下する(当たり前だが)。
- - - -
effective_tld_names.dat のサイズは 58KB程度ある。大したサイズではないが iPhone/iPod touch で実行する場合はどなんだろうか。メンテナンス性は落ちるが NSMutableSet の内容をそのまま plist へ落としておいてそれを使い回すと実行時間とメモリ効率は良くなる。
2009年9月5日土曜日
クッキーのドメイン問題への対処 ... Mozzila Effective TLD Service
クッキーのドメインにまつわる問題として Cookie Monster がある。
この件について調査しているのだが決定的な方法はなくブラウザの対応状況もまちまちなようだ。
クッキーについて、ドメインの指定で、...
(上記ページに参考になるリンクがいくつかある)
この問題に取り組んでいる人も居た。
ワイルド過ぎる realm のワイルドカードを何とかしたい - 前編
情報を集めていくうちに Mozzilaが Effective TLD Service なるものを提供していることがわかった。ここでは TLD だけでなく SLD(Second Level Domain) が汎用(co.jpなど)かどうかが判断できるファイルを提供している。
mxr.mozilla.org/mozilla-central/source/netwerk/dns/src/effective_tld_names.dat
jp のあたりを少し引用してみる。
// jp : http://en.wikipedia.org/wiki/.jp
// http://jprs.co.jp/en/jpdomain.html
// Submitted by registry2008-06-11
jp
// jp organizational type names
ac.jp
ad.jp
co.jp
ed.jp
go.jp
gr.jp
lg.jp
ne.jp
or.jp
// jp geographic type names
// http://jprs.jp/doc/rule/saisoku-1.html
*.aichi.jp
*.akita.jp
*.aomori.jp
*.chiba.jp
:
:
!metro.tokyo.jp
!pref.aichi.jp
!pref.akita.jp
!pref.aomori.jp
:
:
co.jp などのSLDの他、 *.tokyo.jp などの地域型ドメインが列挙されている。
! は、ワイルドカードの除外パターンを表す。
なお前掲のサイトの検証によれば漏れている gLTD もあるようだ。
仮に利用するとしたら、クッキーにこのファイルに掲載されているドメインが指定されている場合は受け取らない、という使い方ができる。
(例)
jp => NG
.jp => NG
co.jp => NG
.co.jp => NG
xcatsan.co.jp => OK
xcatsan.jp => OK
.xcatsan.jp => OK
xcatsan.aichi.jp => NG (*.aichi.jp の指定による)
pref.aichi.jp => OK (!pref.aichi.jp の指定による)
- - - -
ライセンスは MPL Version 1.1 (Mozilla Public License) が使える。これをプログラムに埋め込んで使う方向で考えよう。
2009年9月4日金曜日
クッキーでドメイン=.co.jp を送ってみたら..
サーバに下記のような PHPプログラムをおいて自前実装(XCHTTPCookieStorage)でクッキー受取りの挙動を確認してみた。
<?php
setcookie("testname", "testvalue", 0, "/", ".co.jp");
?>
<html>
hello
</html>
上記 PHPへアクセスすると .co.jp のクッキーが送られてくる。
実行して見ると見事に?受け取れていた。
2009-09-04 05:17:22.750 CookieStorage[3498:80f] (
<NSHTTPCookie version:0 name:@"testname" value:@"testvalue"
expiresDate:@"(null)" created:@"273626241.352053"
sessionOnly:TRUE domain:@".co.jp" path:@"/"
secure:FALSE comment:@"(null)" commentURL:@"(null)" portList:(null)>
)
しかもリクエストURLは xxxx.co.jp ですらない(xxxx.jp というサイト)。つまりクッキー送出元のドメインのチェックも行われていない。試しにまったく無関係なサイト(例えば .yahoo.co.jp)を送出しても受け取っていた。
NSHTTPCookie がそこまできめ細やかなチェックはやらないか。クラスの役割を考えると妥当と言えば妥当だ。
やはりクッキーのドメインチェックは自分でやる必要がありそうだ。
チェックは受取時にすべきだろう。そうすれば送出しないし、ファイルなどへの保存もしない。
主なチェック項目はこんな感じ。
・送出元サーバのドメインとのチェック(無関係なドメインのクッキーは受け取らない)
・gTLDや .の数、その他のチェック
しかし .co.jp と.xcatsan.jp の区別をどうするか。最低限日本のドメインはいいとして海外はどうする?
2009年9月3日木曜日
クッキー(Cookie)のドメインについて
クッキーを受け取る、送る時の重要な条件の一つとしてドメインチェックがある。
例えば、http://www1.xcatsan.co.jp/ から Domain=www1.xcatsan.co.jp のクッキーを受け取った後、このサイトへアクセスする時にはこのクッキーを送る。またhttp://www2.xcatsan.co.jp/ へアクセスする時にはこのクッキーは送らない。
一方、Domain=.xcatsan.co.jp が送られてきた場合には、http://www1.xcatsan.co.jp/, http://www2.xcatsan.co.jp/ 共にこのクッキーを送る。
問題なのは Domain=.co.jp といったクッキーが送られてきた場合。このようなクッキーをもしブラウザが受け取ってしまうと、先の2つのサイトはもちろん http://www.yahoo.co.jp/ や http://www.apple.co.jp/ といった .co.jp を持つすべてのサイトに対してこのクッキーを送ってしまう。
これはセキュリティの問題と認識されていて様々なサイトでも取り上げられている。
(ややふるい情報ばかりだが)
- IEやMozillaなどにセキュリティ・ホール,なりすましを許す可能性あり
- 第3回 犯罪者に代わって操作させられる「セッション・フィクセーション」
- Cookie Monster
- intel.co.jp のクッキーがおかしい
ドメインのルールについては以前紹介した [Studyng HTTP] HTTP Cookies で
DOMAIN_NAME に指定できる文字列は、トップレベルドメインが
"com", "edu", "net", "org", "gov", "mil", "int" の場合はピリオドが2つ以上、
それ以外の場合はピリオドが3つ以上含まれていなければいけません。
従って例えば、domain=jp 等と指定する事はできません。
となっていた。
これを具体的な例に落とすと
.com => NG(.が1個)
xcatsan.com => NG(.が1個)
.xcatsan.com => OK(.が2個)
.co.jp => NG(非gTLDで .が2個)
xcatsan.co.jp => NG(非gTLDで .が2個)
.xcatsan.co.jp => OK(非gTLDで .が3個)
.xcatsan.jp => NG(非gTLDで .が2個)
となる。
ただ最近は xcatsan.jp のようなドメインも存在する。
実際 Safari4のクッキー(Cookies.plist)を見ると、.xcatsan.jp のような非gTLDでかつ . が2個のケースも許容していた。
となると .co.jp (NG) と .xcatsan.jp (OK) の区別は gTLDと . の数だけの判断だけではできなくて、TLD別に細かく判断していく必要がある(JPドメインの場合、co や ne といった定義済みドメインなのか、そうでないか、といった判断)。
うーむ。これを自前で実装するのは面倒だし、国毎のドメイン事情が変わった場合などの後々のメンテナンスも難しい。できれば ルール適用のロジックは Foudation Framework側(すなわち NSHTTPCookie)にまかせたい。
現在の自前実装では、クッキーの受取は基本的に NSHTTPCookie まかせになっている。 そこでもしここで Safari4と同等のルール(すなわち、.co.jp は NGで、.xcatsan.jp は OK)を適用しているならそれにまかせて送出時には特になにもしない(今のままの)実装としよう。
この辺りの挙動を調べてみよう。
(そもそも Safari4の挙動はどうなっているのか?)
2009年9月2日水曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (7)クッキー送出のロジックを改良〜実装
ざっくりとした実装ができた。
仕様は以前紹介した通り。
Cocoaの日々
NSHTTPCookieStorage相当のクラスを自前で実装する (6)クッキー送出のロジックを改良
サンプル:CookieStorage-3.zip
まずはクッキー取得から。
XCHTTPCookieStorage.m
- (void)setCookie:(NSHTTPCookie *)cookie
{
NSString* key1 = [cookie domain];
NSString* key2 = [NSString stringWithFormat:@"%@/%@", [cookie path], [cookie name]];
NSMutableDictionary* cookies2 = [_cookies valueForKey:key1];
if (!cookies2) {
cookies2 = [NSMutableDictionary dictionary];
[_cookies setValue:cookies2 forKey:key1];
}
[cookies2 setValue:cookie forKey:key2];
}
ドメインをキーにして辞書を作成する。この辞書には path/name をキーとしてクッキーを格納する。
次に取得。変更がはいったポイントのみ解説する。
- (NSArray *)cookiesForURL:(NSURL *)URL
{
NSMutableArray* return_cookies = [NSMutableArray array];
NSString* url_path = [URL path];
NSString* url_domain = [URL host];
NSDate* date = [NSDate date];
BOOL is_secure = [[URL scheme] isEqualToString:@"https"];
NSArray* domain_parts = [url_domain componentsSeparatedByString:@"."];
int count = [domain_parts count];
if (count < 1) {
return return_cookies;
}
int index = [domain_parts count]-1;
NSString* part = [domain_parts objectAtIndex:index];
NSString* check_domain = [NSString stringWithFormat:@".%@", part];
index--;
while (index >=0) {
part = [domain_parts objectAtIndex:index];
if (index) {
check_domain = [NSString stringWithFormat:@".%@%@", part, check_domain];
} else {
check_domain = [NSString stringWithFormat:@"%@%@", part, check_domain];
}
index--;
NSDictionary* cookies2 = [_cookies valueForKey:check_domain];
if (!cookies2) {
continue;
}
for (NSHTTPCookie* cookie in [cookies2 allValues]) {
:
(以下、ドメインを除くチェックが続く)
渡されたリクエストURLを .(ドット)で分解し、これを順番に組み立ててチェックしていく。
(イメージ)mail.google.co.jp の場合:
.co.jp
.google.co.jp
mail.google.co.jp
最後の完全一致を除き、先頭に .(ドット)をつけて検索する。なお以前の仕様では検索順序が(詳細 >> 汎用)だったが、これを逆(汎用 >> 詳細)にした。この辺りの仕様はちょっとからないのだが、異なるドメインで同じ path+name が存在した時にはパスと同じように詳細な方を優先させた方が良いと判断してこのような順序とした。
実行してみよう。

ログインできた(クッキー取得と送出がうまくいっている、と思われる)。
- - - -
なお今のままではドメインが .co.jp を持つクッキーもマッチしてしまう。汎用ドメイン(.xcatsan.comなど)を想定してドメインの単語が2つ以上を許しているが .co.jp などはどの組織にも属さない情報となるのでセキュリティ上問題となる。ここでちゃんとチェックするか、あるいはクッキー保存時にそのようなドメインを持つクッキーを受け付けない様にする必要がある(あるいは両方)。
2009年9月1日火曜日
Webページをアクセスしている場合でも webView:resource:willSendRequest:redirectResponse:fromDataSource: のresponseにNSHTTPURLResponse以外が渡ってくることがある
WebViewを使ってGoogleMapsを見ていると WebResourceLoadDelegate のメソッドで例外が出ていた。
2009-09-01 12:53:21.225 CookieStorage[23839:10b]
<NSURLResponse: 0x6a680c0>, about:blank
2009-09-01 12:53:21.246 CookieStorage[23839:10b]
*** -[NSURLResponse allHeaderFields]: unrecognized selector
sent to instance 0x6a680c0
2009-09-01 12:53:21.266 CookieStorage[23839:10b]
*** WebKit discarded an uncaught exception in the
webView:resource:didReceiveResponse:fromDataSource:
delegate:
<NSInvalidArgumentException> *** -[NSURLResponse allHeaderFields]:
unrecognized selector sent to instance 0x6a680c0
プログラムではデリゲートメソッド webView:resource:willSendRequest:redirectResponse:fromDataSource:の responseを NSHTTPURLResponse でキャストしてHTTPヘッダ情報などにアクセスしている。
[(NSHTTPURLResponse*)response allHeaderFields]
ところが例外が出ているケースでは URLがどうも "about:blank" のようなものになっており、この場合は型が NSURLResponse になっているのが分かった。存在しないメソッドを呼び出していたので例外が起きていた。もしかすると以前のクラッシュはこれが原因かもしれない。
対応は responseの型をチェックし、NSHTTPURLResponse の場合のみHTTPヘッダ情報へアクセスするようにした。
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
:
}- - - -
考えてみるとWebブラウジングしていても、リンクが http:// 以外のケース(ftp:// や feed:// など)もあるので当然想定すべきだったか。
2009年8月31日月曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (6)クッキー送出のロジックを改良
前回までは URL に適合するクッキーの検索は、格納してある全てのクッキーを対象に行っていた。これではクッキーの数が増えた場合に効率が悪いので少し改良を加える。複雑なことはやらず、できるだけ単純にしてみよう。
具体的にはドメインをキーとしたハッシュ(辞書)を作り、その下に実際のクッキー配列を格納するようにする。
(イメージ)
NSMutableDictionary
|
|--key:".xcatsan.com"
| value: NSMutableDictionary
| |-- key:"/xxx", value={path="/", name="xxx", ..}
| |-- key:"/some/yyy", value={path="/some", name="yyy", ..}
| :
|
|--key:".co-co-adays.com"
| value: NSMutableDictionary
| |-- key:"/aaa", value={path="/", name="aaa", ..}
| |-- key:"/some/bbb", value={path="/some", name="bbb", ..}
| :
ドメイン名をキーとして取得できる値は NSMutableDictionary とする。path+name をキーとして実際のクッキーをその値として登録する。これによって domain+path+name でユニークが保たれる。
なおドメインは後方一致もありなので、単純なキー検索だけでは漏れが生じてしまう。
この為、検索時にはドメインの完全一致に加えて、後方一致も加える。
(イメージ)
www.japan.xcatsan.com のクッキーを検索する。
(1) www.japan.xcatsan.com をキーにして検索
(2) .japan.xcatsan.com をキーにして検索
(3) .xcatsan.com をキーにして検索
上記でヒットする配列群を前回までのロジックでチェックする。
(続く)
2009年8月29日土曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (5)クッキー送出ではより詳細な方を返す
クッキー送出の処理で、同じドメイン内で同じ名前を持つクッキーがある場合はより詳細な方を返す。
例)pathが "/acme" と "/acme/ammo" で同じ nameの Cookieが存在する場合、
a) リクエストURLのパスが "/acme/ammo/test.html" の場合、後者を返す
b) リクエストURLのパスが "/acme/parts/some.html" の場合、前者を返す
前回のコードをこれに合わせて書き換えてみる。
XCHTTPCookieStorage.m
- (NSArray *)cookiesForURL:(NSURL *)URL
{
// NSMutableArray* return_cookies = [NSMutableArray array];
NSMutableDictionary* cookie_dicts = [NSMutableDictionary dictionary];
:
NSHTTPCookie* stored_cookie;
if (stored_cookie = [cookie_dicts valueForKey:cookie_name]) {
if ([cookie_path length] <= [[stored_cookie path] length]) {
continue;
}
}
// [return_cookies addObject:cookie];
[cookie_dicts setObject:cookie forKey:cookie_name];
}
// return return_cookies;
return [cookie_dicts allValues];
}
該当するクッキーを格納するクラスを、NSMutableArrayをやめて NSMuableDictionaryに変更する。キーにクッキーの名前を使うことで同一のドメイン内のクッキーの名前はユニークになる。同一の名前が存在する場合は、詳細な方=すなわちパスが長い方を選ぶようにした。
2009年8月28日金曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (4)クッキー送出に成功
どうにかこうにかクッキーの送出に成功した。
Googleのログイン画面でID,パスワードを入力すると、
無事にログイン状態となった。
ハマったのは前回紹介したリダイレクト時のクッキーの扱いと、謎のクラッシュ。後者はいろいろ手を入れている間に直ってしまった。もしかすると NSLog() で表示しているオブジェクトに問題があったのかもしれない(NSLog()を一通り消した後、改善していたので)。
サンプル:CookieStorage-2.zip
以下、コード解説。
まず XCHTTPCookieStorageを使う側、WebResourceLoadDelegate のメソッドから。
AppController.m
- (NSURLRequest *)webView:(WebView *)sender
resource:(id)identifier
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
fromDataSource:(WebDataSource *)dataSource
{
[(NSMutableURLRequest*)request setHTTPShouldHandleCookies:NO];
if (redirectResponse) {
[self _setCookiesWithResponse:(NSHTTPURLResponse*)redirectResponse];
}
// NSLog(@"%@", [request URL]);
NSArray* cookies =
[[XCHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[request URL]];
if ([cookies count] > 0) {
NSDictionary* cookie_fields =
[NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
[(NSMutableURLRequest*)request setAllHTTPHeaderFields:cookie_fields];
}
return request;
}
リダイレクト時には redirectResponse が非nilになるので、サーバから受け取ったクッキーを _setCookiesWithResponse: を使い保存する。
URLに適合するクッキーが存在する場合は、-[NSMutableURLRequest setAllHTTPHeaderFields:]を使い、リクエストヘッダに "Cookie"ヘッダを追加する。
レスポンスは _setCookiesWithResponse: を読んでクッキーを保存するだけ。
- (void)webView:(WebView *)sender
resource:(id)identifier
didReceiveResponse:(NSURLResponse *)response
fromDataSource:(WebDataSource *)dataSource
{
[self _setCookiesWithResponse:(NSHTTPURLResponse*)response];
}
_setCookiesWithResponse: の実装はこう。
- (void)_setCookiesWithResponse:(NSHTTPURLResponse*)response
{
NSDictionary* headers = [(NSHTTPURLResponse*)response allHeaderFields];
NSURL* URL = [response URL];
NSArray* cookies =
[NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:URL];
if ([cookies count] > 0) {
[[XCHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies
forURL:URL
mainDocumentURL:URL];
}
}
次は XCHTTPCookieStorage の実装〜URLに合ったクッキーを返す処理。
※以前紹介したクッキーの送出ルールはまだ完全に実装できていない。
XCHTTPCookieStorage.m
- (NSArray *)cookiesForURL:(NSURL *)URL
{
NSMutableArray* return_cookies = [NSMutableArray array];
NSString* url_host = [URL host];
NSString* url_path = [URL path];
NSDate* date = [NSDate date];
BOOL is_secure = [[URL scheme] isEqualToString:@"https"];
for (NSHTTPCookie* cookie in [_cookies allValues]) {
NSString* cookie_path = [cookie path];
NSString* cookie_domain = [cookie domain];
NSDate* cookie_expires_date = [cookie expiresDate];
// secure
if ([cookie isSecure] && !is_secure) {
continue;
}
// expires
if (cookie_expires_date &&
[date compare:cookie_expires_date] == NSOrderedDescending) {
continue;
}
// domain
//*TODO* cookie_domain: .www.google.com
if ([cookie_domain hasPrefix:@"."]) {
if (![url_host hasSuffix:cookie_domain]) {
continue;
}
} else if (![url_host isEqualToString:cookie_domain]) {
continue;
}
// path
if (![cookie_path isEqualToString:@"/"]) {
if (![url_path isEqualToString:cookie_path]) {
if (![url_path hasPrefix:[cookie_path stringByAppendingString:@"/"]]) {
continue;
}
}
}
[return_cookies addObject:cookie];
}
return return_cookies;
}
格納されているクッキーを一つ一つ、セキュリティ、有効期限、ドメイン、パス、とチェックしていく。
#この単純な実装ではクッキーが多くなると時間がかかるので工夫が必要だな。
- - - -
とりあえずクッキーの受け取りと送出の基本動作ができるようになった。この後はクッキーの仕様に合った実装を行って完成度を上げていき、最後に永続化処理を入れて完成させる。
2009年8月27日木曜日
リダイレクト時のクッキー受け取り
クッキーの自前ハンドリングを行っていてどうも受け取れていないクッキーが存在する。調べていると次のことが分かった。
1. 通常のレスポンス(200)の場合は、WebResourceLoadDelegateのメソッド が呼び出され、引数で渡される response からサーバが送出したクッキーを取り出すことができる。
2. リダイレクト(301等)の場合は、webView:resource:didReceiveResponse:fromDataSource: が呼ばれない。クッキーは webView:resource:willSendRequest:redirectResponse:fromDataSource: で渡される redirectResponse から取得する。
Googleのログインなどではリダイレクト時にもクッキーを送出しているのでこれも拾ってやる必要がある。
- - - -
クッキー送出の実装は苦戦中。上記問題が片付いたが、Googleへログインすると何故か強制終了してしまう...
Debugger() was called!
The Debugger has exited due to signal 2 (SIGINT).The Debugger has exited due to signal 2 (SIGINT).
2009年8月26日水曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (3)クッキーの送出
今回は受け取ったクッキーを送出する処理を加える。
前回のサンプルは受け取りだけしか行わないので、そのまま google.cmのログイン処理を行っても警告が出てログインできない。
今回のゴールはクッキーを無事送出してログインできるようにすること。
[Studying HTTP] HTTP Cookiesを参考に送出のルールをまとめてみた。
Cookie送出ルール
1. expires(有効期限)< 現在日時
2. domain がリクエストURLのホスト名と後方一致する
(例)domainが ".xcatsan.com" の場合、"www.xcatsan.com" はOK
3. path がリクエストURLのパスと前方一致する(ただしパスの区切りを考慮)
(例)pathが "/foo" の場合、"/foo/bar.html" は OK
pathが "/foo" の場合、"/foooo/bar.html" は NG (*)
4. secure が TRUEの場合、HTTPSのみ OK
5. 同じ domain 内で、同じ name の Cookieが存在する場合は、
より詳細な方を返す。
(例)pathが "/acme" と "/acme/ammo" で同じ nameの Cookieが存在する場合、
a) リクエストURLのパスが "/acme/ammo/test.html" の場合、後者を返す
b) リクエストURLのパスが "/acme/parts/some.html" の場合、前者を返す
上記をざっくりと実装してみよう。
2009年8月25日火曜日
NSHTTPCookieStorage相当のクラスを自前で実装する (2)クッキーの受け取り
最初から保存やら同期やらすべてを実装すると大変なので、まずはメモリ上だけでクッキーのやりとりを行う実装から始めよう。当面はラフな実装で動作確認を先行して行い、徐々に精度を高めていくやり方で進める。
今回はクッキーの受け取り処理を実装する。
(1) クライアント側の用意
まず WebView の WebResourceLoadDelegate として AppController を指定し、必要なデリゲートメソッドを実装する。
リクエスト処理では、setHTTPShouldHandleCookies を使い、デフォルトの NSHTTPCookieStorage の使用を中止する。
AppController.m
// WebResourceLoadDelegate
- (NSURLRequest *)webView:(WebView *)sender
resource:(id)identifier
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
fromDataSource:(WebDataSource *)dataSource
{
[(NSMutableURLRequest*)request setHTTPShouldHandleCookies:NO];
return request;
}
続いてレスポンス処理では、受け取ったヘッダ情報を XCHTTPCookieStorageへ渡す。
- (void)webView:(WebView *)sender
resource:(id)identifier
didReceiveResponse:(NSURLResponse *)response
fromDataSource:(WebDataSource *)dataSource
{
NSDictionary* headers = [(NSHTTPURLResponse*)response allHeaderFields];
NSURL* URL = [response URL];
NSArray* cookies =
[NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:URL];
[[XCHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies
forURL:URL
mainDocumentURL:URL];
}
(2) XCHTTPCookieStorage の実装
XCHTTPCookieStorage.m
初期化。
- (id)init
{
if (self = [super init]) {
_cookie_accept_policy = NSHTTPCookieAcceptPolicyAlways;
_cookies = [[NSMutableDictionary alloc] init];
}
return self;
}
インスタンス取得(シングルトン)。
+ (XCHTTPCookieStorage *)sharedHTTPCookieStorage
{
static XCHTTPCookieStorage* _cookie_storage = nil;
if (!_cookie_storage) {
_cookie_storage = [[XCHTTPCookieStorage alloc] init];
}
return _cookie_storage;
}
渡されたクッキーを内部処理する(保存する)。
- (void)setCookies:(NSArray *)cookies forURL:(NSURL *)URL mainDocumentURL:(NSURL *)mainDocumentURL
{
for (NSHTTPCookie *cookie in cookies) {
[self setCookie:cookie];
}
}
_keyForCookie: で生成される文字列をキーとして、NSMutableDictionaryへ格納する。
- (void)setCookie:(NSHTTPCookie *)cookie
{
[_cookies setValue:cookie forKey:[self _keyForCookie:cookie]];
}
_keyForCookie: はドメイン名、パス、名前を '/'区切りで連結したものを返す。この辺りは以前参考にした mySTEP の方法を借用した。
- (NSString*)_keyForCookie:(NSHTTPCookie*)cookie
{
return [NSString stringWithFormat:@"%@/%@/%@",
[cookie domain], [cookie path], [cookie name]];
}
実行時に NSDictionary に格納しているクッキーが見られるように「print cookies」ボタンを追加し、-[AppController printCookies:] へ接続する。

- (IBAction)printCookies:(id)sender
{
NSLog(@"%@", [[XCHTTPCookieStorage sharedHTTPCookieStorage] cookies]);
}
実行してみよう。
google.comを開く。

この時のクッキーは次の通り。既に1つ受け取っている。

ページの右上にある「ログイン」リンクを押す。

クッキーを見ると、1つ増えている。

サンプル:
CookieStorage-1.zip
- - - -
粗い実装だがまずは受け取りができるようなった。次はクッキー送出に取りかかってみる。
