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

[インデックス 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.gohtmlify.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つの側面に集約されます。

  1. URLパス処理の改善と正規表現による堅牢なバリデーション:

    • lenPath 定数の廃止: 以前は /view/ のようなプレフィックスの長さを const lenPath = len("/view/") のように定義し、r.URL.Path[lenPath:] でタイトルを抽出していました。この方法は、異なるプレフィックス(/edit/, /save/ など)を持つハンドラごとに同様の定数を定義する必要があり、冗長でエラーを起こしやすいものでした。
    • regexp.MustCompilevalidPath の導入: 新しいアプローチでは、var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$") という単一の正規表現を導入しました。
      • ^/: パスが / で始まることを意味します。
      • (edit|save|view): パスが editsave、または 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パスのバリデーションとタイトル抽出が単一の堅牢なメカニズムに統合され、コードの重複が削減され、不正なパスに対するハンドリングが改善されました。
  2. テストプロセスのクリーンアップと改善:

    • srcextract.go および htmlify.go の削除: これらのGoプログラムは、チュートリアルのHTMLページにGoコードスニペットを埋め込むために使用されていましたが、このコミットで削除されました。これは、これらのツールが不要になったか、あるいはよりシンプルな方法(例えば、index.html 内の {{code}} ディレクティブの直接的な変更)で同様の機能が実現されるようになったことを示唆しています。
    • Makefile の変更:
      • CLEANFILES から srcextract.binhtmlify.bin が削除され、final-test.bina.out が追加されました。これは、ビルド成果物のクリーンアップ対象が変更されたことを意味します。
      • index.html のビルドステップから srcextract.binhtmlify.bin への依存が削除されました。
    • test.bash の改善:
      • クリーンアップ対象に a.outget.bin が追加されました。
      • -all オプションが追加されました。このオプションが指定された場合、doc/articles/wiki ディレクトリ内のすべての .go ファイルに対して go build -o a.out $fn が実行されます。これにより、チュートリアルに含まれるすべてのGoコードスニペットがコンパイル可能であることを確認できるようになり、テストの網羅性が向上しました。

これらの変更は、Go Wikiチュートリアルのコードベースの品質と保守性を向上させ、より堅牢なWebアプリケーション開発のベストプラクティスを示すことに貢献しています。

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

このコミットにおけるコアとなるコードの変更は、主に以下のファイルに見られます。

  1. 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() {
    
  2. doc/articles/wiki/htmlify.go および doc/articles/wiki/srcextract.go:

    • これらのファイルは完全に削除されました。
  3. doc/articles/wiki/Makefile:

    • CLEANFILES の定義が変更され、不要なバイナリのクリーンアップが削除され、新しいテスト関連のバイナリが追加されました。
    • index.html のビルドステップから srcextract.binhtmlify.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)
    
  4. 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/ といったプレフィックスの長さに合わせて手動で設定されていました。このアプローチは、以下のような問題点がありました。

  1. 冗長性: 各ハンドラで異なるプレフィックスを扱う場合、それぞれに対応する lenPath 定数や同様のスライス操作が必要となり、コードが重複しやすくなります。
  2. 脆弱性: パスが期待される形式でない場合(例: /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 を使って正規表現がコンパイルされます。この正規表現は、/ で始まり、その後に editsaveview のいずれかが続き、さらに / の後に英数字のページタイトルが続くパスに正確にマッチします。
    • ([a-zA-Z0-9]+) の部分がページタイトルをキャプチャするグループです。
  • getTitle 関数(または同様のロジックを持つ makeHandler 内のクロージャ)では、validPath.FindStringSubmatch(r.URL.Path) を呼び出します。
    • この関数は、正規表現にマッチする部分文字列と、キャプチャグループにマッチした部分文字列をスライスの形で返します。
    • もしパスが正規表現にマッチしない場合 (m == nil)、http.NotFound を呼び出して404エラーを返し、エラーを返します。これにより、不正なURLパスに対する堅牢なエラーハンドリングが実現されます。
    • マッチした場合、m[2] には正規表現の2番目のキャプチャグループ、つまりページタイトルが格納されているため、これを返します。

この変更により、URLパスの解析とバリデーションが単一の、より表現力豊かで堅牢なメカニズムに統合されました。これにより、コードの可読性と保守性が向上し、アプリケーションのセキュリティも強化されました。

また、htmlify.gosrcextract.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は直接リンクとして機能します。)

参考にした情報源リンク