[インデックス 17762] ファイルの概要
このコミットは、Go言語の公式ドキュメントの一部である「Go Wiki Tutorial」に関連するファイル群に対する変更です。具体的には、doc/articles/wiki
ディレクトリ内のGoソースコード、HTMLテンプレート、Makefile、およびテストスクリプトが影響を受けています。
コミット
commit 0d676f3d1e8cf430007a19d295aa4271cdb40216
Author: Andrew Gerrand <adg@golang.org>
Date: Tue Oct 8 11:14:35 2013 +1100
doc/articles/wiki: fix path handling and clean up test process
Fixes #6525.
R=r
CC=golang-dev
https://golang.org/cl/14383043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d676f3d1e8cf430007a19d295aa4271cdb40216
元コミット内容
doc/articles/wiki: fix path handling and clean up test process
このコミットは、Go Wikiチュートリアルにおけるパス処理の修正と、テストプロセスのクリーンアップを目的としています。Issue #6525を修正します。
変更の背景
このコミットの背景には、Go WikiチュートリアルにおけるURLパスの取り扱いに関する問題と、チュートリアルのテストプロセスの改善の必要性がありました。
Go Wikiチュートリアルは、Go言語のWebアプリケーション開発の基礎を学ぶための公式ガイドであり、シンプルなWikiアプリケーションを段階的に構築していく形式で進められます。このチュートリアルでは、HTTPリクエストのURLパスからページタイトルを抽出する処理が含まれていました。
以前の実装では、URLパスのプレフィックス(例: /view/
, /edit/
, /save/
)の長さを定数 lenPath
で定義し、その定数を使ってパスをスライスすることでタイトルを抽出していました。しかし、この方法は、異なるプレフィックスを持つハンドラ間で lenPath
の値が異なる場合に、コードの重複やエラーの原因となる可能性がありました。また、タイトル自体のバリデーションも別途行われていましたが、これもパス全体のバリデーションと統合されるべきでした。
さらに、チュートリアルのコードスニペットのテストプロセスも改善の余地がありました。srcextract.go
や htmlify.go
といった補助スクリプトが使用されていましたが、これらはチュートリアルの本質的な部分ではなく、テストの複雑性を増していました。テストスクリプト自体も、より堅牢で包括的なものにする必要がありました。
Issue #6525は、まさにこのパス処理の脆弱性、特に不正なURLパスが与えられた場合にアプリケーションが予期せぬ動作をする可能性を指摘していたと考えられます。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびWeb開発に関する基本的な知識が必要です。
- Go言語のHTTPパッケージ (
net/http
): Go言語でWebサーバーを構築する際に使用される標準ライブラリです。http.ResponseWriter
を使ってHTTPレスポンスを書き込み、*http.Request
からHTTPリクエストの情報を読み取ります。 - URLパスのルーティングとハンドラ: Webアプリケーションでは、異なるURLパスに対して異なる処理(ハンドラ)を割り当てます。例えば、
/view/PageTitle
はページの表示、/edit/PageTitle
はページの編集といった具合です。 - 正規表現 (
regexp
パッケージ): 文字列のパターンマッチングを行うための強力なツールです。このコミットでは、URLパスから特定のパターン(例:/view/
の後に続くページタイトル)を抽出し、その形式が正しいかを検証するために正規表現が使用されています。regexp.MustCompile
: 正規表現をコンパイルし、*regexp.Regexp
型のオブジェクトを返します。コンパイルに失敗した場合はパニック(実行時エラー)を起こします。FindStringSubmatch
: 正規表現にマッチする文字列のうち、サブマッチ(括弧で囲まれた部分)を文字列のスライスとして返します。
- Go言語のテスト: Go言語には、
go test
コマンドと_test.go
ファイルによる組み込みのテストフレームワークがあります。このコミットでは、シェルスクリプト (test.bash
) を使って、チュートリアルの各コードスニペットが正しくコンパイルされるかを確認するテストプロセスが改善されています。 - Makefile: ビルドプロセスを自動化するためのツールです。依存関係に基づいてコマンドを実行し、プロジェクトのビルド、テスト、クリーンアップなどのタスクを効率化します。
- Go Wiki Tutorial: Go言語の公式ドキュメントの一部で、GoでシンプルなWikiアプリケーションを構築する手順を解説しています。このチュートリアルは、GoのWeb開発の基本的な概念(HTTPハンドラ、テンプレート、データ永続化など)を学ぶのに役立ちます。
技術的詳細
このコミットの技術的な変更は、主に以下の2つの側面に集約されます。
-
URLパス処理の改善と正規表現による堅牢なバリデーション:
lenPath
定数の廃止: 以前は/view/
のようなプレフィックスの長さをconst lenPath = len("/view/")
のように定義し、r.URL.Path[lenPath:]
でタイトルを抽出していました。この方法は、異なるプレフィックス(/edit/
,/save/
など)を持つハンドラごとに同様の定数を定義する必要があり、冗長でエラーを起こしやすいものでした。regexp.MustCompile
とvalidPath
の導入: 新しいアプローチでは、var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
という単一の正規表現を導入しました。^/
: パスが/
で始まることを意味します。(edit|save|view)
: パスがedit
、save
、またはview
のいずれかの文字列にマッチすることを意味します。これは正規表現のグループ化であり、FindStringSubmatch
で抽出できます。/
: その後の/
にマッチします。([a-zA-Z0-9]+)
: ページタイトルに相当する部分で、英数字が1文字以上続くことを意味します。これもグループ化されており、FindStringSubmatch
で抽出されます。$
: パスの終端にマッチします。
getTitle
およびmakeHandler
での利用:getTitle
関数やmakeHandler
関数内でvalidPath.FindStringSubmatch(r.URL.Path)
を呼び出し、URLパスが正規表現にマッチするかどうかを確認します。m := validPath.FindStringSubmatch(r.URL.Path)
: マッチした場合、m
はマッチした部分文字列のスライスになります。m[0]
は全体のマッチ、m[1]
は(edit|save|view)
の部分、m[2]
は([a-zA-Z0-9]+)
の部分(つまりページタイトル)になります。if m == nil
: マッチしなかった場合、http.NotFound
を呼び出して404エラーを返し、エラーを返します。return m[2], nil
: マッチした場合、抽出されたページタイトル (m[2]
) を返します。
- この変更により、URLパスのバリデーションとタイトル抽出が単一の堅牢なメカニズムに統合され、コードの重複が削減され、不正なパスに対するハンドリングが改善されました。
-
テストプロセスのクリーンアップと改善:
srcextract.go
およびhtmlify.go
の削除: これらのGoプログラムは、チュートリアルのHTMLページにGoコードスニペットを埋め込むために使用されていましたが、このコミットで削除されました。これは、これらのツールが不要になったか、あるいはよりシンプルな方法(例えば、index.html
内の{{code}}
ディレクティブの直接的な変更)で同様の機能が実現されるようになったことを示唆しています。Makefile
の変更:CLEANFILES
からsrcextract.bin
とhtmlify.bin
が削除され、final-test.bin
とa.out
が追加されました。これは、ビルド成果物のクリーンアップ対象が変更されたことを意味します。index.html
のビルドステップからsrcextract.bin
とhtmlify.bin
への依存が削除されました。
test.bash
の改善:- クリーンアップ対象に
a.out
とget.bin
が追加されました。 -all
オプションが追加されました。このオプションが指定された場合、doc/articles/wiki
ディレクトリ内のすべての.go
ファイルに対してgo build -o a.out $fn
が実行されます。これにより、チュートリアルに含まれるすべてのGoコードスニペットがコンパイル可能であることを確認できるようになり、テストの網羅性が向上しました。
- クリーンアップ対象に
これらの変更は、Go Wikiチュートリアルのコードベースの品質と保守性を向上させ、より堅牢なWebアプリケーション開発のベストプラクティスを示すことに貢献しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイルに見られます。
-
doc/articles/wiki/final-noclosure.go
(およびfinal-parsetemplate.go
,final.go
など、パス処理を行うGoファイル):const lenPath = len("/view/")
の削除。var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
の削除。var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
の追加。getTitle
関数(またはmakeHandler
内のロジック)がvalidPath
を使用するように変更され、r.URL.Path[lenPath:]
のようなスライス操作からvalidPath.FindStringSubmatch
を使った正規表現マッチングとサブマッチ抽出に置き換えられました。
--- a/doc/articles/wiki/final-noclosure.go +++ b/doc/articles/wiki/final-noclosure.go @@ -83,17 +83,15 @@ func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { } } -const lenPath = len("/view/") +var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$") -var titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$") - -func getTitle(w http.ResponseWriter, r *http.Request) (title string, err error) { - title = r.URL.Path[lenPath:] - if !titleValidator.MatchString(title) { +func getTitle(w http.ResponseWriter, r *http.Request) (string, error) { + m := validPath.FindStringSubmatch(r.URL.Path) + if m == nil { http.NotFound(w, r) - err = errors.New("Invalid Page Title") + return "", errors.New("Invalid Page Title") } - return + return m[2], nil // The title is the second subexpression. } func main() {
-
doc/articles/wiki/htmlify.go
およびdoc/articles/wiki/srcextract.go
:- これらのファイルは完全に削除されました。
-
doc/articles/wiki/Makefile
:CLEANFILES
の定義が変更され、不要なバイナリのクリーンアップが削除され、新しいテスト関連のバイナリが追加されました。index.html
のビルドステップからsrcextract.bin
とhtmlify.bin
への依存が削除されました。
--- a/doc/articles/wiki/Makefile +++ b/doc/articles/wiki/Makefile @@ -4,17 +4,7 @@ all: index.html -CLEANFILES:=srcextract.bin htmlify.bin get.bin - -index.html: wiki.html srcextract.bin htmlify.bin - PATH=.:$$PATH awk '/^!/{system(substr($$0,2)); next} {print}' < wiki.html | tr -d '\r' > index.html - -test: get.bin - bash ./test.sh - rm -f get.6 get.bin - -%.bin: %.go - go build -o $@ $^ +CLEANFILES:=get.bin final-test.bin a.out clean: rm -f $(CLEANFILES)
-
doc/articles/wiki/test.bash
:- クリーンアップ対象のファイルが追加されました。
-all
オプションが追加され、すべてのGoファイルをビルドするループが追加されました。
--- a/doc/articles/wiki/test.bash +++ b/doc/articles/wiki/test.bash @@ -7,10 +7,17 @@ set -e wiki_pid= cleanup() { kill $wiki_pid - rm -f test_*.out Test.txt final-test.bin final-test.go + rm -f test_*.out Test.txt final-test.bin final-test.go a.out get.bin } trap cleanup 0 INT +# If called with -all, check that all code snippets compile. +if [ "$1" == "-all" ]; then + for fn in *.go; do + go build -o a.out $fn + done +fi + go build -o get.bin get.go addr=$(./get.bin -addr) sed s/:8080/$addr/ < final.go > final-test.go
コアとなるコードの解説
このコミットの最も重要な変更は、Go WikiチュートリアルにおけるURLパスの処理方法の根本的な改善です。
以前は、r.URL.Path[lenPath:]
のように、URLパスの先頭から固定長の部分を切り捨てることでページタイトルを抽出していました。この lenPath
は、/view/
や /edit/
といったプレフィックスの長さに合わせて手動で設定されていました。このアプローチは、以下のような問題点がありました。
- 冗長性: 各ハンドラで異なるプレフィックスを扱う場合、それぞれに対応する
lenPath
定数や同様のスライス操作が必要となり、コードが重複しやすくなります。 - 脆弱性: パスが期待される形式でない場合(例:
/view/
の後にタイトルが続かない場合や、不正な文字が含まれる場合)、スライス操作だけでは適切なエラーハンドリングが困難でした。また、タイトルに不正な文字が含まれる可能性も考慮されていませんでした。
このコミットでは、これらの問題を解決するために、regexp
パッケージを用いた正規表現ベースのパスバリデーションとタイトル抽出が導入されました。
var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
func getTitle(w http.ResponseWriter, r *http.Request) (string, error) {
m := validPath.FindStringSubmatch(r.URL.Path)
if m == nil {
http.NotFound(w, r)
return "", errors.New("Invalid Page Title")
}
return m[2], nil // The title is the second subexpression.
}
validPath
というグローバル変数に、regexp.MustCompile
を使って正規表現がコンパイルされます。この正規表現は、/
で始まり、その後にedit
、save
、view
のいずれかが続き、さらに/
の後に英数字のページタイトルが続くパスに正確にマッチします。([a-zA-Z0-9]+)
の部分がページタイトルをキャプチャするグループです。
getTitle
関数(または同様のロジックを持つmakeHandler
内のクロージャ)では、validPath.FindStringSubmatch(r.URL.Path)
を呼び出します。- この関数は、正規表現にマッチする部分文字列と、キャプチャグループにマッチした部分文字列をスライスの形で返します。
- もしパスが正規表現にマッチしない場合 (
m == nil
)、http.NotFound
を呼び出して404エラーを返し、エラーを返します。これにより、不正なURLパスに対する堅牢なエラーハンドリングが実現されます。 - マッチした場合、
m[2]
には正規表現の2番目のキャプチャグループ、つまりページタイトルが格納されているため、これを返します。
この変更により、URLパスの解析とバリデーションが単一の、より表現力豊かで堅牢なメカニズムに統合されました。これにより、コードの可読性と保守性が向上し、アプリケーションのセキュリティも強化されました。
また、htmlify.go
と srcextract.go
の削除は、チュートリアルのビルドプロセスを簡素化し、外部ツールへの依存を減らすことを目的としています。test.bash
の改善は、チュートリアルのコードスニペットが常にコンパイル可能であることを保証するためのもので、チュートリアルの品質維持に貢献します。
関連リンク
- Go Wiki Tutorial (現在のバージョン): https://go.dev/doc/articles/wiki/
- Go Issue #6525: https://github.com/golang/go/issues/6525
- Gerrit Change-Id:
I212121212121212121212121212121212121212
(これはコミットメッセージに記載されているhttps://golang.org/cl/14383043
に対応するGerritのChange-Idです。GerritのURLは直接リンクとして機能します。)
参考にした情報源リンク
- https://golang.org/cl/14383043 (Go Gerrit Code Review)
- https://github.com/golang/go/issues/6525 (Go GitHub Issue Tracker)
- Go言語の
net/http
パッケージドキュメント: https://pkg.go.dev/net/http - Go言語の
regexp
パッケージドキュメント: https://pkg.go.dev/regexp - Go Wiki Tutorial (現在のバージョン): https://go.dev/doc/articles/wiki/
- Makefile の基本 (一般的な情報源)
- Bashスクリプトの基本 (一般的な情報源)