[インデックス 15239] ファイルの概要
このコミットは、Go言語の実験的な exp/cookiejar
パッケージにおいて、HTTPクッキーの管理機能とそれに対応するテストスイートを実装したものです。具体的には、http.CookieJar
インターフェースの Cookies
メソッドの実装と、クッキーのドメインマッチング、パスマッチング、ソートロジックが追加されています。また、テストスイートはChromiumプロジェクトから移植されたテストケースを含んでおり、実装の堅牢性を高めています。
コミット
commit 8e7d156237cc1409aa6b955cf4307d2c4992ab29
Author: Volker Dobler <dr.volker.dobler@gmail.com>
Date: Thu Feb 14 19:41:58 2013 +1100
exp/cookiejar: implement Cookies and provided tests
This CL provides the implementation of Cookies and
the complete test suite. Several tests have been ported
from the Chromium project as a cross check.
R=nigeltao, rsc, bradfitz
CC=golang-dev
https://golang.org/cl/7311073
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8e7d156237cc1409aa6b955cf4307d2c4992ab29
元コミット内容
exp/cookiejar: implement Cookies and provided tests
この変更は、Cookies
メソッドの実装と完全なテストスイートを提供します。いくつかのテストは、相互チェックとしてChromiumプロジェクトから移植されました。
変更の背景
このコミットが行われた当時、Go言語の標準ライブラリには、HTTPクッキーを効率的かつRFCに準拠して管理するための包括的な CookieJar
の実装が不足していました。net/http
パッケージには CookieJar
インターフェースは存在していましたが、その具体的な実装は提供されていませんでした。ウェブアプリケーションやHTTPクライアントが適切にクッキーを処理するためには、クッキーの保存、取得、有効期限、ドメイン/パスのマッチング、セキュリティ属性(Secure, HttpOnly)の考慮など、複雑なロジックが必要となります。
このコミットは、これらの要件を満たす exp/cookiejar
パッケージの一部として、http.CookieJar
インターフェースの主要メソッドである Cookies
の実装を提供することを目的としています。特に、クッキーの選択ロジック(どのクッキーをリクエストに含めるべきか)と、RFC 6265に準拠したソート順序の実装が重要な課題でした。また、実装の正確性を保証するために、既存の堅牢なクッキー管理システムであるChromiumプロジェクトのテストケースを移植し、相互検証を行うことが決定されました。
前提知識の解説
HTTPクッキー (HTTP Cookies)
HTTPクッキーは、ウェブサーバーがユーザーのウェブブラウザに送信する小さなデータ片です。ブラウザはこれらのクッキーを保存し、同じサーバーへの後続のリクエストでそれらを送り返します。これにより、サーバーはユーザーの状態を記憶したり、ユーザーを識別したりすることができます。
クッキーには様々な属性があります。
- Name-Value Pair:
name=value
の形式で、クッキーのデータ本体です。 - Expires / Max-Age: クッキーの有効期限を指定します。
Expires
は特定の日時、Max-Age
は現在からの秒数で指定します。これらが設定されていない場合、セッションクッキーとなり、ブラウザを閉じると削除されます。 - Domain: クッキーが送信されるドメインを指定します。例えば
example.com
と設定すると、example.com
およびそのサブドメイン(www.example.com
,sub.example.com
など)に送信されます。先頭にドットがない場合(例:www.example.com
)、そのホスト名にのみ送信されます。 - Path: クッキーが送信されるパスを指定します。例えば
/docs
と設定すると、/docs
およびそのサブパス(/docs/index.html
など)に送信されます。 - Secure: この属性が設定されている場合、クッキーはHTTPS接続でのみ送信されます。
- HttpOnly: この属性が設定されている場合、JavaScriptからクッキーにアクセスできなくなります。これにより、クロスサイトスクリプティング (XSS) 攻撃によるクッキーの盗難を防ぐことができます。
- SameSite: クロスサイトリクエストフォージェリ (CSRF) 攻撃を防ぐための属性です。このコミットの時点ではまだ広く普及していませんでしたが、現代のウェブでは重要なセキュリティ属性です。
RFC 6265
RFC 6265は、HTTP State Management Mechanism、すなわちHTTPクッキーの動作を定義する標準仕様です。このRFCは、クッキーのセット、保存、送信に関する詳細なルールを定めており、特にドメインマッチング、パスマッチング、クッキーのソート順序など、複雑なロジックの基準となります。このコミットでは、RFC 6265のセクション5.1.3 (domain-match) とセクション5.1.4 (path-match)、およびセクション5.4 (クッキーのソート) に厳密に準拠した実装が行われています。
Go言語の net/http
および net/url
パッケージ
net/http
: Go言語のHTTPクライアントおよびサーバーを構築するための主要なパッケージです。http.Cookie
構造体やhttp.CookieJar
インターフェースなどが定義されています。net/url
: URLの解析と構築を行うためのパッケージです。クッキーのドメインやパスのマッチングには、URLのホスト名やパスを正確に解析する能力が不可欠です。
Chromiumプロジェクト
Chromiumは、Google Chromeブラウザのオープンソース基盤です。Chromiumプロジェクトは、ウェブ技術の最先端を実装しており、そのクッキー管理システムは非常に堅牢で、RFCに厳密に準拠しています。このコミットでChromiumのテストケースを移植したことは、Goの cookiejar
実装の正確性と互換性を検証するための重要な手段となっています。
技術的詳細
このコミットの主要な技術的詳細は、exp/cookiejar
パッケージ内の Jar
型の Cookies
メソッドの実装と、それに付随するヘルパー関数の追加にあります。
-
Cookies
メソッドの実装:http.CookieJar
インターフェースのCookies(u *url.URL) []*http.Cookie
メソッドを実装しています。このメソッドは、指定されたURLに対して送信すべきクッキーのリストを返します。- まず、URLのスキームがHTTPまたはHTTPSでない場合は空のスライスを返します。これはセキュリティ上の理由と、クッキーが主にHTTP/HTTPSで使用されるためです。
Jar
内部のクッキーエントリ (entry
型) を走査し、現在の時刻 (now
) と比較して有効期限切れの永続クッキーを削除します。shouldSend
ヘルパー関数を使用して、各クッキーが現在のホストとパスに対して送信可能かどうかを判断します。- 送信可能なクッキーを
selected
スライスに集めます。 - RFC 6265のセクション5.4に従い、
selected
スライス内のクッキーをソートします。このソートは、パスの長さが長いものから順に行われ、パスの長さが同じ場合は作成時刻が古いものから順に行われます。 - ソートされたクッキーを
*http.Cookie
型に変換し、結果として返します。
-
ヘルパー関数の追加:
shouldSend(https bool, host, path string) bool
:- クッキーエントリ
e
が、指定されたhost
とpath
に対して送信されるべきかを判断します。 domainMatch
とpathMatch
の両方がtrue
であり、かつリクエストがHTTPSであるか、またはクッキーがSecure属性を持たない場合にtrue
を返します。
- クッキーエントリ
domainMatch(host string) bool
:- RFC 6265のセクション5.1.3「domain-match」を実装します。
- クッキーの
Domain
属性がホストと完全に一致するか、またはクッキーがホストオンリーでない場合に、ホストがクッキーのドメインのサブドメインであるかをチェックします。
pathMatch(requestPath string) bool
:- RFC 6265のセクション5.1.4「path-match」を実装します。
- リクエストパスがクッキーの
Path
属性と完全に一致するか、またはリクエストパスがクッキーのパスで始まり、かつクッキーのパスがスラッシュで終わるか、リクエストパスの次の文字がスラッシュであるかをチェックします。
byPathLength
型とsort.Interface
実装:byPathLength
は[]entry
型のエイリアスで、sort.Interface
インターフェース(Len
,Less
,Swap
メソッド)を実装します。Less
メソッドは、RFC 6265のセクション5.4のポイント2に従い、パスの長さが長いものから順に、次に作成時刻が古いものから順にソートするロジックを提供します。
-
テストスイートの拡充:
jar_test.go
に大量のテストケースが追加されています。updateAndDeleteTests
: クッキーの更新と削除に関するテスト。Max-Age
やExpires
属性による削除、Secureフラグのクリア、異なるドメインやパスでのクッキーの挙動などを検証します。TestExpiration
: クッキーの有効期限切れが正しく処理されるかを検証します。chromiumBasicsTests
,chromiumDomainTests
,chromiumDeletionTests
: Chromiumプロジェクトのcookie_store_unittest.h
から移植されたテストケース群です。これらは、ドメインマッチングの厳密なルール、IPアドレスに対するクッキーの挙動、非ドットホスト名とTLDの処理、ドメイン属性のケースインセンシティブ性、パスのマッチングなど、非常に詳細なエッジケースをカバーしています。これにより、Goのcookiejar
実装がウェブの現実世界で遭遇する様々なシナリオに正確に対応できることが保証されます。
コアとなるコードの変更箇所
src/pkg/exp/cookiejar/jar.go
--- a/src/pkg/exp/cookiejar/jar.go
+++ b/src/pkg/exp/cookiejar/jar.go
@@ -11,6 +11,7 @@ import (
"net"
"net/http"
"net/url"
+ "sort"
"strings"
"sync"
"time"
@@ -97,6 +98,52 @@ func (e *entry) id() string {
return fmt.Sprintf("%s;%s;%s", e.Domain, e.Path, e.Name)
}
+// shouldSend determines whether e's cookie qualifies to be included in a
+// request to host/path. It is the caller's responsibility to check if the
+// cookie is expired.
+func (e *entry) shouldSend(https bool, host, path string) bool {
+ return e.domainMatch(host) && e.pathMatch(path) && (https || !e.Secure)
+}
+
+// domainMatch implements "domain-match" of RFC 6265 section 5.1.3.
+func (e *entry) domainMatch(host string) bool {
+ if e.Domain == host {
+ return true
+ }
+ return !e.HostOnly && strings.HasSuffix(host, "."+e.Domain)
+}
+
+// pathMatch implements "path-match" according to RFC 6265 section 5.1.4.
+func (e *entry) pathMatch(requestPath string) bool {
+ if requestPath == e.Path {
+ return true
+ }
+ if strings.HasPrefix(requestPath, e.Path) {
+ if e.Path[len(e.Path)-1] == '/' {
+ return true // The "/any/" matches "/any/path" case.
+ } else if requestPath[len(e.Path)] == '/' {
+ return true // The "/any" matches "/any/path" case.
+ }
+ }
+ return false
+}
+
+// byPathLength is a []entry sort.Interface that sorts according to RFC 6265
+// section 5.4 point 2: by longest path and then by earliest creation time.
+type byPathLength []entry
+
+func (s byPathLength) Len() int { return len(s) }
+
+func (s byPathLength) Less(i, j int) bool {
+ in, jn := len(s[i].Path), len(s[j].Path)
+ if in == jn {
+ return s[i].Creation.Before(s[j].Creation)
+ }
+ return in > jn
+}
+
+func (s byPathLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
// Cookies implements the Cookies method of the http.CookieJar interface.
//
// It returns an empty slice if the URL's scheme is not HTTP or HTTPS.
@@ -118,10 +165,28 @@ func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
return cookies
}
+ now := time.Now()
+ https := u.Scheme == "https"
+ path := u.Path
+ if path == "" {
+ path = "/"
+ }
+
modified := false
- for _, _ = range submap {
- // TODO: handle expired cookies
- // TODO: handle selection of cookies
+ var selected []entry
+ for id, e := range submap {
+ if e.Persistent && !e.Expires.After(now) {
+ delete(submap, id)
+ modified = true
+ continue
+ }
+ if !e.shouldSend(https, host, path) {
+ continue
+ }
+ e.LastAccess = now
+ submap[id] = e
+ selected = append(selected, e)
+ modified = true
}
if modified {
if len(submap) == 0 {
@@ -131,7 +196,10 @@ func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
}
}
- // TODO: proper sorting based on Path length (and Creation)
+ sort.Sort(byPathLength(selected))
+ for _, e := range selected {
+ cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value})
+ }
return cookies
}
src/pkg/exp/cookiejar/jar_test.go
このファイルには、TestBasics
、TestUpdateAndDelete
、TestExpiration
、TestChromiumBasics
、TestChromiumDomain
、TestChromiumDeletion
など、多数の新しいテストケースが追加されています。特に注目すべきは、Chromiumプロジェクトから移植されたテストケース群です。
コアとなるコードの解説
jar.go
の変更点
import "sort"
の追加: クッキーをRFC 6265の規定に従ってソートするために、Go標準ライブラリのsort
パッケージがインポートされました。shouldSend
関数:- この関数は、特定の
entry
(クッキー) が、与えられたhost
とpath
のリクエストに対して送信されるべきかどうかを判断します。 e.domainMatch(host)
: クッキーのドメインがリクエストのホストと一致するかをチェックします。e.pathMatch(path)
: クッキーのパスがリクエストのパスと一致するかをチェックします。(https || !e.Secure)
: リクエストがHTTPSであるか、またはクッキーがSecure
属性を持たない場合にtrue
となります。これにより、Secure
クッキーはHTTPSでのみ送信され、非Secure
クッキーはHTTP/HTTPSの両方で送信されるというルールが適用されます。
- この関数は、特定の
domainMatch
関数:- RFC 6265のセクション5.1.3「domain-match」アルゴリズムを実装しています。
e.Domain == host
: クッキーのドメインとリクエストのホストが完全に一致する場合。!e.HostOnly && strings.HasSuffix(host, "."+e.Domain)
: クッキーがホストオンリーでない(つまり、ドメインクッキーである)場合、リクエストのホストがクッキーのドメインのサブドメインであるかをチェックします。例えば、クッキーのドメインがexample.com
で、ホストがwww.example.com
の場合、この条件がtrue
になります。
pathMatch
関数:- RFC 6265のセクション5.1.4「path-match」アルゴリズムを実装しています。
requestPath == e.Path
: リクエストパスとクッキーのパスが完全に一致する場合。strings.HasPrefix(requestPath, e.Path)
: リクエストパスがクッキーのパスで始まる場合。e.Path[len(e.Path)-1] == '/'
: クッキーのパスがスラッシュで終わる場合(例:/any/
は/any/path
にマッチ)。requestPath[len(e.Path)] == '/'
: クッキーのパスがスラッシュで終わらないが、リクエストパスの次の文字がスラッシュである場合(例:/any
は/any/path
にマッチ)。
byPathLength
型とsort.Interface
実装:- これは、クッキーをソートするためのカスタムソートロジックを提供します。
Less(i, j int) bool
: 比較ロジックを定義します。- まず、パスの長さで比較し、長いパスを持つクッキーが優先されます (
in > jn
)。 - パスの長さが同じ場合は、クッキーの作成時刻 (
Creation
) で比較し、古いクッキーが優先されます (s[i].Creation.Before(s[j].Creation)
)。
- まず、パスの長さで比較し、長いパスを持つクッキーが優先されます (
- このソート順序は、RFC 6265のセクション5.4「The User Agent Sends Cookies to the Origin Server」のポイント2に厳密に準拠しています。これにより、より具体的なパスを持つクッキーが、より一般的なパスを持つクッキーよりも優先して送信されるようになります。
Cookies
メソッドのロジック:now := time.Now()
: 現在時刻を取得し、有効期限切れのクッキーを判断するために使用します。https := u.Scheme == "https"
: リクエストがHTTPSかどうかを判断します。path := u.Path; if path == "" { path = "/" }
: リクエストパスが空の場合、デフォルトでルートパス/
を使用します。- クッキーエントリの
submap
をイテレートします。e.Persistent && !e.Expires.After(now)
: 永続クッキーが有効期限切れの場合、submap
から削除し、modified
フラグをtrue
に設定します。!e.shouldSend(https, host, path)
:shouldSend
関数がfalse
を返す場合、そのクッキーは選択されません。e.LastAccess = now
: クッキーがアクセスされた時刻を更新します。selected = append(selected, e)
: 送信対象となるクッキーをselected
スライスに追加します。
sort.Sort(byPathLength(selected))
:selected
スライス内のクッキーを、上記で定義したbyPathLength
のロジックに従ってソートします。- ソートされた
selected
クッキーを*http.Cookie
型に変換し、最終的な結果スライスcookies
に追加して返します。
jar_test.go
の変更点
jarTest
構造体のrun
メソッドの変更:jar.content()
メソッドが削除され、テスト内で直接クッキーの内容を文字列に変換するロジックが追加されました。これにより、テストの独立性が高まり、Jar
の内部実装に依存しない形で内容を検証できるようになりました。jar.SetCookies
の呼び出しでmustParseURL(test.fromURL)
を直接使用するように変更され、冗長な変数u
が削除されました。t.Errorf()
の呼び出しがコメントアウトされていた箇所が有効化され、Cookies
メソッドの実装が完了したことを示しています。
- 新しいテストケースの追加:
updateAndDeleteTests
: クッキーの更新、削除(Max-Age=-1
や過去のExpires
)、Secureフラグの挙動、異なるドメインやパスでのクッキーの相互作用など、動的なクッキー管理のシナリオを網羅しています。TestExpiration
: 時間経過によるクッキーの有効期限切れが正しく処理されることを検証します。- Chromiumプロジェクトからの移植テスト:
chromiumBasicsTests
,chromiumDomainTests
,chromiumDeletionTests
は、Chromiumブラウザのクッキー管理のテストスイートから厳選されたものです。これらは、以下のような非常に詳細なエッジケースをカバーしています。- ドメイン属性の末尾のドットの扱い。
- 有効なサブドメインと無効なドメインのマッチング。
- IPアドレスに対するクッキーの挙動。
- 非ドットホスト名とトップレベルドメイン (TLD) の処理。
- ドメイン属性のケースインセンシティブ性。
- パスのマッチングの厳密なルール。
- ホストクッキーとドメインクッキーの挙動の違い。
- セッションクッキーと永続クッキーの削除ロジック。
これらのテストケースは、Goの cookiejar
実装がRFC 6265に準拠し、実際のウェブ環境で期待される堅牢な動作をすることを保証するために不可欠です。
関連リンク
- RFC 6265 - HTTP State Management Mechanism: https://datatracker.ietf.org/doc/html/rfc6265
- Go言語
net/http
パッケージ: https://pkg.go.dev/net/http - Go言語
net/url
パッケージ: https://pkg.go.dev/net/url
参考にした情報源リンク
- Chromium
cookie_store_unittest.h
(当時のバージョン): http://src.chromium.org/viewvc/chrome/trunk/src/net/cookies/cookie_store_unittest.h?revision=159685&content-type=text/plain- このリンクは当時のChromiumのテストファイルの具体的なリビジョンを示しており、Goの
cookiejar
テストがここから移植されたことを裏付けています。
- このリンクは当時のChromiumのテストファイルの具体的なリビジョンを示しており、Goの
- Goのコードレビューシステム (Gerrit) のCL (Change List) ページ: https://golang.org/cl/7311073
- このリンクは、このコミットに対応するGoの公式コードレビューページであり、詳細な議論や変更履歴を確認できます。
- HTTP Cookie - MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies
- HTTPクッキーの基本的な概念と属性について理解を深めるための一般的な情報源。
- Go言語の
sort
パッケージ: https://pkg.go.dev/sort- Goにおけるソートインターフェースとカスタムソートの実装方法に関する情報。