Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 19197] ファイルの概要

このコミットは、Go言語の標準ライブラリであるnet/httpパッケージにおけるCookieの取り扱いを改善するものです。具体的には、RFC 6265で本来許可されていないCookieの値に含まれるカンマとスペースを許容するように変更されました。これにより、現実世界のブラウザの挙動や、RFCに厳密には準拠していないが広く利用されているCookieとの相互運用性が向上します。値がカンマやスペースで始まる、または終わる場合には、誤解釈を防ぐために値が引用符で囲まれて送信されるようになります。

コミット

commit ed88076c6437fa87c26a568b46020eacc9202e13
Author: Volker Dobler <dr.volker.dobler@gmail.com>
Date:   Wed Apr 16 23:01:02 2014 -0700

    net/http: allow commas and spaces in cookie values
    
    According to RFC 6265 a cookie value may contain neither
    commas nor spaces but such values are very common in the
    wild and browsers handle them very well so we'll allow
    both commas and spaces.
    Values starting or ending in a comma or a space are
    sent in the quoted form to prevent missinterpetations.
    
    RFC 6265 conforming values are handled as before and
    semicolons, backslashes and double-quotes are still
    disallowed.
    
    Fixes #7243
    
    LGTM=nigeltao
    R=nigeltao
    CC=bradfitz, golang-codereviews
    https://golang.org/cl/86050045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ed88076c6437fa87c26a568b46020eacc9202e13

元コミット内容

net/http: allow commas and spaces in cookie values

According to RFC 6265 a cookie value may contain neither
commas nor spaces but such values are very common in the
wild and browsers handle them very well so we'll allow
both commas and spaces.
Values starting or ending in a comma or a space are
sent in the quoted form to prevent missinterpetations.

RFC 6265 conforming values are handled as before and
semicolons, backslashes and double-quotes are still
disallowed.

Fixes #7243

LGTM=nigeltao
R=nigeltao
CC=bradfitz, golang-codereviews
https://golang.org/cl/86050045

変更の背景

HTTP Cookieの仕様はRFC 6265によって厳密に定義されており、Cookieの値(cookie-value)にはカンマ(,)やスペース( )を含めることが許可されていません。しかし、現実世界のWebアプリケーションやブラウザの実装においては、これらの文字がCookie値に含まれるケースが非常に多く見られます。多くのブラウザは、RFCの規定に反していても、これらの文字を含むCookie値を問題なく処理します。

Go言語のnet/httpパッケージは、これまでの実装ではRFC 6265の規定に厳密に従っていたため、カンマやスペースを含むCookie値を不正なものとして扱っていました。この厳格な挙動は、RFCに準拠していないが広く普及しているCookieを使用するシステムとの間で相互運用性の問題を引き起こしていました。例えば、Goで書かれたサーバーが、カンマやスペースを含むCookieを送信するクライアントからのリクエストを正しく処理できない、あるいはGoサーバーが生成したCookieが他のシステムで正しく解釈されないといった問題が発生していました。

このコミットは、このような現実世界との乖離を解消し、Goのnet/httpパッケージがより堅牢で実用的なHTTPクライアントおよびサーバーとして機能するようにするために行われました。RFCの規定を緩和し、カンマとスペースをCookie値に許容することで、既存のWebシステムとの互換性を高めることが目的です。ただし、セミコロン(;)、バックスラッシュ(\)、ダブルクォート(")といった、より構造的な意味を持つ文字は引き続き禁止されます。

コミットメッセージには Fixes #7243 と記載されていますが、Goの公式GitHubリポジトリでは直接この番号のIssueを見つけることはできませんでした。これは、Issueが非常に古いものであるか、別のトラッカーで管理されていた可能性を示唆しています。しかし、このコミットの目的は、現実のWeb環境におけるCookieの多様な利用実態に対応することにあると理解できます。

前提知識の解説

HTTP Cookieは、WebサーバーがユーザーのWebブラウザに送信する小さなデータ片です。ブラウザはこれを受け取り、通常は保存し、同じサーバーへの後続のリクエストとともに送り返します。Cookieは主に以下の目的で使用されます。

  • セッション管理: ユーザーのログイン状態、ショッピングカートの内容など。
  • パーソナライゼーション: ユーザー設定、テーマなど。
  • トラッキング: ユーザーの行動追跡、広告表示など。

Cookieは通常、「名前-値」のペアと、有効期限、パス、ドメイン、Secure、HttpOnlyなどの属性で構成されます。

RFC 6265 (HTTP State Management Mechanism)

RFC 6265は、HTTP Cookieの動作を定義するIETF(Internet Engineering Task Force)の標準仕様です。このRFCは、Cookieの構文、セマンティクス、セキュリティに関する詳細を規定しています。

特に、Cookieの値(cookie-value)の構文については厳密な定義があります。RFC 6265のセクション4.1.1では、cookie-octetという概念が導入されており、これはCookie値に含めることができる文字の集合を定義しています。この定義によると、Cookie値はUS-ASCII文字のうち、制御文字(CTLs)、空白文字(whitespace)、ダブルクォート(")、カンマ(,)、セミコロン(;)、バックスラッシュ(\)を除くものとされています。

つまり、RFC 6265に厳密に従う場合、Cookie値にカンマやスペースを含めることはできません。

ブラウザの実装と現実世界

RFC 6265は理想的な仕様を記述していますが、実際のWebブラウザの実装は、歴史的な経緯や相互運用性の必要性から、RFCの規定を完全に厳守しない場合があります。特に、カンマやスペースを含むCookie値は、多くのブラウザで問題なく処理されることが知られています。これは、Webの進化の過程で、RFCが策定される以前から存在していた非標準的なCookieの利用方法に対応するためと考えられます。

このような現実世界の挙動とRFCの間の乖離は、「堅牢性の原則(Postel's Law)」の一例と見なすことができます。これは、「自分が送るものは厳密に、他人が受け取るものは寛容に」という考え方です。ブラウザは、サーバーから送られてくる非標準的なCookieに対しても寛容に振る舞うことで、Webの互換性を維持しています。

しかし、サーバー側の実装がRFCに厳密すぎると、ブラウザが送信する非標準的なCookieを拒否したり、誤って解釈したりする問題が発生します。このコミットは、Goのnet/httpパッケージが、この「堅牢性の原則」をより適切に適用し、現実世界のWeb環境との互換性を高めることを目指しています。

技術的詳細

このコミットの主要な変更点は、net/httpパッケージ内のCookieの値を処理するロジック、特にsanitizeCookieValue関数とvalidCookieValueByte関数の挙動の変更、およびparseCookieValue関数の簡素化です。

  1. validCookieValueByte関数の変更:

    • この関数は、Cookie値として有効なバイトを判定します。
    • 変更前は、0x20 < b && b < 0x7f && b != '"' && b != ',' && b != ';' && b != '\\' という条件で、スペース(0x20)とカンマ(,)を無効な文字としていました。
    • 変更後は、0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' となり、スペース(0x20)とカンマ(,)が有効な文字として含まれるようになりました。これにより、これらの文字がCookie値に直接含まれることが許可されます。
    • ただし、ダブルクォート(")、セミコロン(;)、バックスラッシュ(\)は引き続き無効な文字として扱われます。これらはCookieの構文解析において特別な意味を持つため、値に含めることはできません。
  2. sanitizeCookieValue関数の変更:

    • この関数は、Cookieの値をサニタイズ(無効な文字を除去または変換)し、必要に応じて引用符で囲む役割を担います。
    • 変更前は、単にsanitizeOrWarn関数を呼び出して、validCookieValueByteで無効とされた文字を除去していました。
    • 変更後は、まずsanitizeOrWarnで基本的なサニタイズを行った後、追加のロジックが導入されました。
      • 値が空文字列の場合はそのまま返します。
      • 値の先頭または末尾がスペース( )またはカンマ(,)である場合、その値全体をダブルクォート(")で囲みます。これは、ブラウザがこのような値を正しく解釈できるようにするための措置です。例えば、Value: " a"Value: "\" a\"" として送信されます。
      • 上記以外のRFC 6265に準拠した値(カンマやスペースで始まったり終わったりしない値)は、これまで通り引用符なしで送信されます。
  3. parseCookieValue関数の簡素化と関連関数の削除:

    • 変更前は、parseCookieValueparseCookieExpiresValueparseCookieValueUsingunquoteCookieValueisCookieByteisCookieExpiresByteといった複数の関数が存在し、Cookie値のパースと検証のロジックが分散していました。特にexpires属性のCookie値は、日付形式の特殊性からカンマやスペースを許容するisCookieExpiresByteを使用するなど、異なるパースロジックを持っていました。
    • このコミットにより、parseCookieExpiresValueparseCookieValueUsingunquoteCookieValueisCookieByteisCookieExpiresByteといった関数が削除され、parseCookieValue関数にロジックが集約されました。
    • 新しいparseCookieValue関数は、まず値がダブルクォートで囲まれていればその引用符を剥がします。その後、validCookieValueByte関数を使用して、値の各バイトが有効であるかを検証します。これにより、パースロジックが大幅に簡素化され、一貫性が保たれるようになりました。expires属性のCookie値も、他のCookie値と同様にparseCookieValueで処理されるようになります。

これらの変更により、Goのnet/httpパッケージは、RFC 6265の厳密な解釈から一歩踏み出し、現実世界のWeb環境におけるCookieの多様な利用実態に柔軟に対応できるようになりました。

コアとなるコードの変更箇所

このコミットによる主要なコード変更は、以下のファイルに集中しています。

  • src/pkg/net/http/cookie.go: Cookieの処理ロジックが実装されている主要なファイル。
    • readSetCookies関数内のparseCookieValueFnの呼び出しがparseCookieValueに直接変更され、expires属性に対する特別なパースロジックが削除されました。
    • sanitizeCookieValue関数の実装が大幅に変更され、カンマとスペースの許容と、条件付きの引用符追加ロジックが導入されました。
    • validCookieValueByte関数の条件が変更され、カンマとスペースが有効な文字として追加されました。
    • unquoteCookieValue, isCookieByte, isCookieExpiresByte, parseCookieValueUsing, parseCookieExpiresValueといった複数の補助関数が削除されました。
    • parseCookieValue関数が簡素化され、引用符の剥がしとvalidCookieValueByteによる検証が一元化されました。
  • src/pkg/net/http/cookie_test.go: Cookie処理のテストケースが記述されているファイル。
    • writeSetCookiesTestsreadSetCookiesTestsに、カンマやスペースを含むCookie値、および引用符で囲まれるべきCookie値(先頭または末尾にカンマやスペースがある場合)のテストケースが多数追加されました。
    • TestCookieSanitizeValueにも、新しいsanitizeCookieValueの挙動を検証するテストケースが追加されました。

コアとなるコードの解説

src/pkg/net/http/cookie.go

func sanitizeCookieValue(v string) string の変更

// 変更前:
// func sanitizeCookieValue(v string) string {
// 	return sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
// }

// 変更後:
func sanitizeCookieValue(v string) string {
	v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) // まず基本的なサニタイズ
	if len(v) == 0 {
		return v
	}
	// 値の先頭または末尾がスペースまたはカンマの場合、引用符で囲む
	if v[0] == ' ' || v[0] == ',' || v[len(v)-1] == ' ' || v[len(v)-1] == ',' {
		return `"` + v + `"`
	}
	return v
}

この関数は、Set-Cookieヘッダーでクライアントに送信されるCookieの値を整形します。変更の核心は、RFC 6265では許可されていないカンマやスペースを値に含めることを許容しつつ、それらの文字が値の先頭や末尾にある場合に、ブラウザが正しく解釈できるようにダブルクォートで値を囲むロジックが追加された点です。これにより、GoのHTTPサーバーがより柔軟なCookieを生成できるようになります。

func validCookieValueByte(b byte) bool の変更

// 変更前:
// func validCookieValueByte(b byte) bool {
// 	return 0x20 < b && b < 0x7f && b != '"' && b != ',' && b != ';' && b != '\\'
// }

// 変更後:
func validCookieValueByte(b byte) bool {
	return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
}

この関数は、Cookie値の各バイトが有効な文字であるかをチェックします。変更前はスペース(0x20)とカンマ(,)を無効としていましたが、変更後はこれらを有効な文字として扱うようになりました。これにより、sanitizeCookieValue関数がこれらの文字を削除することなく、値に含めることができるようになります。ただし、ダブルクォート、セミコロン、バックスラッシュは引き続き無効です。

func parseCookieValue(raw string) (string, bool) の変更と関連関数の削除

// 変更前は、unquoteCookieValue, isCookieByte, isCookieExpiresByte, parseCookieValueUsing など複数の関数に分散していた。
// parseCookieValue は parseCookieValueUsing(raw, isCookieByte) を呼び出していた。

// 変更後:
func parseCookieValue(raw string) (string, bool) {
	// 引用符があれば剥がす
	if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
		raw = raw[1 : len(raw)-1]
	}
	// 各バイトが有効かチェック
	for i := 0; i < len(raw); i++ {
		if !validCookieValueByte(raw[i]) {
			return "", false
		}
	}
	return raw, true
}

この関数は、クライアントから受信したCookieの値をパースします。変更前は、expires属性のCookie値に対して特別なパースロジック(カンマやスペースを許容する)が存在していましたが、このコミットによりその特殊性がなくなり、すべてのCookie値が同じparseCookieValue関数で処理されるようになりました。この関数は、まず値が引用符で囲まれていればそれを剥がし、その後、更新されたvalidCookieValueByte関数を使用して、値の各バイトが有効であるかを検証します。これにより、Cookie値のパースロジックが簡素化され、一貫性が向上しました。

src/pkg/net/http/cookie_test.go

テストファイルには、新しい挙動を検証するための多数のテストケースが追加されています。特に注目すべきは、writeSetCookiesTestsreadSetCookiesTestsspecial-プレフィックスの付いたテストケースが追加されたことです。これらは、カンマやスペースを含むCookie値が正しく送信され、また正しく受信できることを確認します。

例:

// writeSetCookiesTests に追加されたテストケース
{
	&Cookie{Name: "special-1", Value: "a z"},
	`special-1=a z`, // スペースを含むが、先頭/末尾ではないので引用符なし
},
{
	&Cookie{Name: "special-2", Value: " z"},
	`special-2=" z"`, // 先頭にスペースがあるので引用符あり
},
{
	&Cookie{Name: "special-5", Value: "a,z"},
	`special-5=a,z`, // カンマを含むが、先頭/末尾ではないので引用符なし
},
{
	&Cookie{Name: "special-6", Value: ",z"},
	`special-6=",z"`, // 先頭にカンマがあるので引用符あり
},

// readSetCookiesTests に追加されたテストケース
{
	Header{"Set-Cookie": {`special-1=a z`}},
	[]*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}},
},
{
	Header{"Set-Cookie": {`special-2=" z"`}},
	[]*Cookie{{Name: "special-2", Value: " z", Raw: `special-2=" z"`}},
},

これらのテストケースは、sanitizeCookieValueが期待通りに引用符を追加したりしなかったりすること、そしてparseCookieValueがそれらの値を正しくパースできることを保証します。

関連リンク

  • Go CL 86050045: https://golang.org/cl/86050045
  • Fixes #7243: このコミットが修正したIssue番号。Goの公式GitHubリポジトリでは直接見つかりませんでしたが、この変更の背景にある問題を示しています。

参考にした情報源リンク