[インデックス 15151] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部として将来的に導入される exp/cookiejar
パッケージの初期インフラストラクチャを構築するものです。具体的には、HTTPクッキーを管理するための基盤となるデータ構造とヘルパー関数、そして http.CookieJar
インターフェースの実装の骨格が追加されています。
コミット
commit 8c6489bc27b639487f56392d13c43f95fab0ac20
Author: Volker Dobler <dr.volker.dobler@gmail.com>
Date: Wed Feb 6 22:37:34 2013 +1100
exp/cookiejar: infrastructure for upcoming implementation
This CL is the first of a handful of CLs which will provide
the implementation of cookiejar. It contains several helper
functions and the skeleton of Cookies and SetCookies.
Proper host name handling requires the ToASCII transformation
from package idna which currently lives in the go.net
subrepo. This CL thus contains just a TODO for this issue.
R=nigeltao, rsc, bradfitz
CC=golang-dev
https://golang.org/cl/7287046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8c6489bc27b639487f56392d13c43f95fab0ac20
元コミット内容
このコミットは、exp/cookiejar
パッケージの今後の実装のためのインフラストラクチャを提供します。これは、cookiejar
の実装を提供する一連の変更リスト(CLs)の最初のものです。いくつかのヘルパー関数と、Cookies
および SetCookies
メソッドの骨格が含まれています。
適切なホスト名処理には、現在 go.net
サブリポジトリにある idna
パッケージからの ToASCII
変換が必要です。この変更リストには、この問題に対する TODO
コメントのみが含まれています。
変更の背景
Webアプリケーションにおいて、HTTPクッキーはセッション管理、ユーザー認証、パーソナライゼーションなど、多岐にわたる機能を実現するために不可欠な要素です。Go言語の net/http
パッケージには http.CookieJar
インターフェースが定義されていますが、その具体的な実装は提供されていませんでした。開発者は独自のクッキー管理ロジックを実装するか、サードパーティのライブラリを使用する必要がありました。
このコミットは、Go標準ライブラリに堅牢で標準に準拠したクッキー管理機能を提供するための第一歩です。特に、RFC 6265(HTTP State Management Mechanism)に厳密に従った実装を目指しており、クッキーの保存、取得、有効期限、ドメイン、パスなどの複雑なルールを適切に処理できる基盤を構築することが目的です。
また、国際化ドメイン名(IDN)のサポートも重要な考慮事項です。IDNは、ASCII文字以外の文字(日本語、中国語、アラビア語など)を含むドメイン名を使用可能にするもので、これらを適切に処理するためには、Punycodeエンコーディング(ToASCII変換)が必要となります。このコミットでは、そのための TODO
が残されており、今後の実装課題として認識されています。
前提知識の解説
HTTPクッキー (HTTP Cookie)
HTTPクッキーは、WebサーバーがユーザーのWebブラウザに送信する小さなデータ片です。ブラウザはこれらのクッキーを保存し、同じサーバーへの後続のリクエストでそれらを送り返します。これにより、サーバーはユーザーの状態を「記憶」することができます。クッキーには、名前、値、ドッキーが有効なドメイン、パス、有効期限、Secureフラグ(HTTPS接続のみで送信)、HttpOnlyフラグ(JavaScriptからのアクセスを禁止)などの属性が含まれます。
http.CookieJar
インターフェース
Go言語の net/http
パッケージで定義されているインターフェースで、HTTPクライアントがクッキーを自動的に管理するために使用されます。このインターフェースは以下の2つのメソッドを定義しています。
Cookies(u *url.URL) []*Cookie
: 指定されたURLに送信すべきクッキーのリストを返します。SetCookies(u *url.URL, cookies []*Cookie)
: 指定されたURLから受信したクッキーのリストを保存します。
RFC 6265 (HTTP State Management Mechanism)
HTTPクッキーの動作を定義する標準仕様です。クッキーの構文、セマンティクス、セキュリティに関する詳細なルールが記述されており、ブラウザやサーバーがクッキーをどのように処理すべきかを定めています。このコミットで実装される cookiejar
は、このRFCに準拠することを目指しています。
Public Suffix List (PSL)
パブリックサフィックスリストは、ドメイン名の「パブリックサフィックス」(例: .com
, .co.uk
, .jp
など)を列挙したリストです。これは、セキュリティとプライバシーの目的で非常に重要です。例えば、example.com
と sub.example.com
は同じ組織が管理するドメインですが、example.com
と example.co.uk
は異なる組織が管理する可能性があります。PSLを使用することで、クッキーが意図しないドメインに設定されたり、悪意のあるサブドメインが他のサブドメインのクッキーを盗んだりするのを防ぐことができます。PublicSuffixList
インターフェースは、与えられたドメインのパブリックサフィックスを返す機能を提供します。
国際化ドメイン名 (IDN) と Punycode (ToASCII)
国際化ドメイン名(IDN)は、ASCII文字セットに含まれない文字(例: bücher.de
)を含むドメイン名です。DNSシステムはASCII文字しか扱えないため、IDNは「Punycode」と呼ばれるエンコーディング方式を使用してASCII互換形式に変換されます。この変換プロセスは「ToASCII」変換と呼ばれます。例えば、bücher.de
は xn--bcher-kva.de
のように変換されます。クッキーのドメイン属性を適切に処理するためには、このToASCII変換が不可欠です。コミットメッセージで言及されている idna
パッケージは、この変換機能を提供します。
技術的詳細
このコミットは、exp/cookiejar
パッケージの jar.go
と jar_test.go
ファイルに大幅な変更を加えています。
jar.go
の変更点
-
インポートの追加:
net
: ホスト名とポートの解析に使用されます。strings
: 文字列操作(小文字化、接尾辞チェックなど)に使用されます。sync
:Jar
構造体のミューテックス (sync.Mutex
) を使用して、並行アクセス時のデータ競合を防ぎます。time
: クッキーの有効期限や作成時刻、最終アクセス時刻を管理するために使用されます。
-
Jar
構造体の拡張:mu sync.Mutex
:Jar
の内部状態(特にentries
マップ)へのアクセスを保護するためのミューテックスが追加されました。これにより、複数のゴルーチンからの同時アクセスに対してスレッドセーフになります。entries map[string]map[string]entry
: クッキーを保存するための主要なデータ構造です。- 外側のマップのキーは「eTLD+1」(effective Top-Level Domain + 1、つまりパブリックサフィックスの1つ上のドメイン)です。これにより、異なるドメインのクッキーが分離されます。
- 内側のマップのキーは、クッキーの名前、ドメイン、パスを組み合わせたもの(
name/domain/path
)とコメントに記載されており、クッキーを一意に識別するために使用されます。 - 値は
entry
構造体です。
-
entry
構造体の追加:- これは、RFC 6265 に定義されているクッキーの属性を内部的に表現するための構造体です。
Name
,Value
,Domain
,Path
,Secure
,HttpOnly
,Persistent
,HostOnly
: これらはHTTPクッキーの標準的な属性に対応します。Expires
,Creation
,LastAccess
: クッキーの有効期限、作成時刻、最終アクセス時刻をtime.Time
型で保持します。これにより、クッキーのライフサイクル管理が可能になります。
-
New
関数の実装:New
関数は、新しいJar
インスタンスを初期化して返します。jar.entries
マップがmake
で初期化されます。Options
が指定されている場合、PublicSuffixList
が設定されます。
-
Cookies
メソッドの骨格実装:http.CookieJar
インターフェースのCookies
メソッドの初期実装です。- URLスキームが
http
またはhttps
でない場合は空のスライスを返します。 canonicalHost
を使用してホスト名を正規化します。jarKey
を使用して、entries
マップから適切なサブマップ (submap
) を取得するためのキーを生成します。- ミューテックス (
j.mu
) を使用して、entries
マップへのアクセスを保護します。 TODO
コメントとして、期限切れクッキーの処理とクッキーの選択ロジックが残されています。TODO
コメントとして、パスの長さと作成時刻に基づく適切なソートが残されています。
-
SetCookies
メソッドの骨格実装:http.CookieJar
インターフェースのSetCookies
メソッドの初期実装です。- クッキーが空の場合、またはURLスキームが
http
またはhttps
でない場合は何もしません。 canonicalHost
を使用してホスト名を正規化します。jarKey
を使用して、entries
マップから適切なサブマップを取得するためのキーを生成します。- ミューテックス (
j.mu
) を使用して、entries
マップへのアクセスを保護します。 TODO
コメントとして、submap
内のエントリの作成、更新、削除ロジックが残されています。
-
ヘルパー関数の追加:
-
canonicalHost(host string) (string, error)
:- RFC 6265 のセクション 5.1.2 で定義されている正規化されたホスト名を返します。
- ポート番号があれば削除し、ホスト名を小文字に変換します。
- 末尾のドット(例:
www.example.com.
)を削除します。 - 重要な
TODO
: RFC 6265 の「正規化されたホスト名」はidna
パッケージのToASCII
変換を必要としますが、このコミットではまだ実装されていません。これは、国際化ドメイン名(IDN)の適切な処理に影響します。
-
hasPort(host string) bool
:- 与えられたホスト文字列にポート番号が含まれているかどうかを判定します。
- IPv6アドレスの形式(
[::1]:8080
)も考慮に入れています。
-
jarKey(host string, psl PublicSuffixList) string
:Jar
のentries
マップで使用するキーを生成します。- ホストがIPアドレスの場合は、IPアドレス自体をキーとします。
PublicSuffixList
が提供されている場合、eTLD+1(effective Top-Level Domain + 1)をキーとして使用します。これは、www.example.com
とsub.example.com
の両方がexample.com
をキーとして共有するようにします。PublicSuffixList
がnil
の場合、ホストのトップレベルドメイン(TLD)をキーとして使用します。PublicSuffixList
が壊れている(例:PublicSuffix
がホスト自体を返す)場合の安全策も含まれています。
-
isIP(host string) bool
:- 与えられたホスト文字列がIPアドレスであるかどうかを判定します。
net.ParseIP
を使用します。
- 与えられたホスト文字列がIPアドレスであるかどうかを判定します。
-
jar_test.go
の変更点
- 新規ファイルの追加:
jar_test.go
が新しく作成され、jar.go
で追加されたヘルパー関数の単体テストが含まれています。 testPSL
構造体:PublicSuffixList
インターフェースのテスト実装です。co.uk
とデフォルトの*
ルールのみを持つ簡略化されたPSLを提供します。
canonicalHostTests
:canonicalHost
関数のテストケースを定義しています。様々な形式のホスト名(大文字小文字、ポート付き、末尾ドット付き、IPアドレス)が正規化されることを確認します。TestCanonicalHost
:canonicalHostTests
を実行し、結果を検証します。TODO
としてエラーハンドリングの追加が示唆されています。hasPortTests
:hasPort
関数のテストケースを定義しています。ポートの有無を正確に判定できることを確認します。TestHasPort
:hasPortTests
を実行し、結果を検証します。jarKeyTests
:jarKey
関数のテストケースを定義しています。testPSL
を使用した場合とnil
の場合の両方で、正しいジャキーが生成されることを確認します。TestJarKey
:jarKeyTests
を実行し、結果を検証します。isIPTests
:isIP
関数のテストケースを定義しています。IPアドレスとそうでない文字列を正確に識別できることを確認します。TestIsIP
:isIPTests
を実行し、結果を検証します。
コアとなるコードの変更箇所
src/pkg/exp/cookiejar/jar.go
Jar
構造体にmu sync.Mutex
とentries map[string]map[string]entry
が追加。entry
構造体が新規定義。New
関数がJar
の初期化ロジックを持つように変更。Cookies
メソッドとSetCookies
メソッドが骨格実装を持つように変更。canonicalHost
,hasPort
,jarKey
,isIP
の各ヘルパー関数が新規追加。
src/pkg/exp/cookiejar/jar_test.go
- 新規ファイルとして追加。
testPSL
構造体と、canonicalHost
,hasPort
,jarKey
,isIP
の各ヘルパー関数のテストケースが定義され、テスト関数が実装されている。
コアとなるコードの解説
このコミットの核心は、Jar
構造体とその内部の entries
マップ、そしてクッキーの内部表現である entry
構造体の導入です。
type Jar struct {
psList PublicSuffixList
// mu locks the remaining fields.
mu sync.Mutex
// entries is a set of entries, keyed by their eTLD+1 and subkeyed by
// their name/domain/path.
entries map[string]map[string]entry
}
type entry struct {
Name string
Value string
Domain string
Path string
Secure bool
HttpOnly bool
Persistent bool
HostOnly bool
Expires time.Time
Creation time.Time
LastAccess time.Time
}
Jar
構造体は、クッキーを保存するための中心的なコンテナです。sync.Mutex
を持つことで、複数のゴルーチンから安全にアクセスできるようになっています。entries
マップは、クッキーを効率的に検索・管理するための階層的な構造を提供します。外側のキーは「eTLD+1」であり、これにより異なるドメインのクッキーが論理的に分離されます。内側のマップは、クッキーの名前、ドメイン、パスの組み合わせで一意に識別される entry
構造体を保持します。
entry
構造体は、RFC 6265 に定義されているクッキーのすべての重要な属性をカプセル化します。これにより、クッキーのライフサイクル(有効期限、作成、最終アクセス)やセキュリティ属性(Secure, HttpOnly)を正確に追跡・管理できます。
ヘルパー関数群は、クッキー管理の複雑な側面を抽象化し、主要な Cookies
および SetCookies
メソッドの実装を簡素化します。
func canonicalHost(host string) (string, error) {
var err error
host = strings.ToLower(host)
if hasPort(host) {
host, _, err = net.SplitHostPort(host)
if err != nil {
return "", err
}
}
if strings.HasSuffix(host, ".") {
// Strip trailing dot from fully qualified domain names.
host = host[:len(host)-1]
}
// TODO: the "canonicalized host name" of RFC 6265 requires the idna ToASCII
// transformation. Possible solutions:
// - promote package idna from go.net to go and import "net/idna"
// - document behavior as a BUG
return host, nil
}
canonicalHost
関数は、クッキーのドメイン属性の比較において非常に重要です。RFC 6265 に従ってホスト名を正規化することで、クッキーが正しいドメインに適用されることを保証します。特に、ポート番号の除去や小文字化、末尾ドットの除去は、クッキーのドメインマッチングルールに不可欠です。しかし、idna
パッケージの ToASCII
変換に関する TODO
は、国際化ドメイン名(IDN)のサポートがまだ不完全であることを示しており、今後の課題として残されています。
func jarKey(host string, psl PublicSuffixList) string {
if isIP(host) {
return host
}
if psl == nil {
// Key cookies under TLD of host.
return host[1+strings.LastIndex(host, "."):]
}
suffix := psl.PublicSuffix(host)
if suffix == host {
return host
}
i := len(host) - len(suffix)
if i <= 0 || host[i-1] != '.' {
// The provided public suffix list psl is broken.
// Storing cookies under host is a safe stopgap.
return host
}
prevDot := strings.LastIndex(host[:i-1], ".")
return host[prevDot+1:]
}
jarKey
関数は、Jar
の内部マップのキーを生成するロジックをカプセル化します。PublicSuffixList
の有無によって異なる戦略を採用しており、PSLが利用可能な場合はeTLD+1をキーとすることで、セキュリティとプライバシーの観点から適切なクッキーの分離を実現しています。これにより、example.com
のクッキーが sub.example.com
にも適用されるが、another.com
には適用されない、といったRFCのルールを遵守するための基盤が作られています。
これらの変更は、exp/cookiejar
パッケージが将来的にGo標準ライブラリに統合されるための、堅牢で拡張性のある基盤を築くものです。
関連リンク
- Go言語の
net/http
パッケージ: https://pkg.go.dev/net/http - RFC 6265 - HTTP State Management Mechanism: https://datatracker.ietf.org/doc/html/rfc6265
- Public Suffix List: https://publicsuffix.org/
- Go言語の
go.net/idna
パッケージ (現在の場所): https://pkg.go.dev/golang.org/x/net/idna
参考にした情報源リンク
- Go言語の公式ドキュメント
- RFC 6265
- Public Suffix List の概念に関する一般的な情報源
- 国際化ドメイン名(IDN)と Punycode に関する一般的な情報源
- GitHub上のGo言語リポジトリのコミット履歴と関連する議論
- Go言語の
net/http/cookiejar
パッケージの現在の実装 (このコミットの後の進化形)- https://pkg.go.dev/net/http/cookiejar
- このコミットは
exp/cookiejar
であり、最終的にnet/http/cookiejar
に昇格しました。