[インデックス 1654] ファイルの概要
このコミットは、Go言語の初期のドキュメンテーションサーバー(GDS: Go Doc Server)の機能強化に関するものです。具体的には、ディレクトリリスティング機能の追加、リンクの動作改善、ロギングの導入、およびパスのサニタイズ処理の強化が含まれています。これにより、GDSはより実用的なドキュメンテーション閲覧ツールへと進化しました。
コミット
commit e08cc15251416dfc63b043edd55e41078aa374fe
Author: Robert Griesemer <gri@golang.org>
Date: Mon Feb 9 21:05:14 2009 -0800
Some real GDS functionality:
- directory listings w/ working links
- some links working in source code (most don't do the right thing yet)
- use of logging
R=r
OCL=24728
CL=24728
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e08cc15251416dfc63b043edd55e41078aa374fe
元コミット内容
Some real GDS functionality:
- directory listings w/ working links
- some links working in source code (most don't do the right thing yet)
- use of logging
R=r
OCL=24728
CL=24728
変更の背景
このコミットは、Go言語の初期開発段階において、Goソースコードのドキュメンテーションをブラウザ経由で提供するためのサーバー(GDS)の機能性を向上させることを目的としています。当時のGDSは基本的なファイル表示機能しか持たず、ディレクトリの内容を一覧表示したり、ソースコード内のリンクを適切に処理したりする能力が不足していました。
開発者は、Go言語のコードベースが拡大するにつれて、その構造を理解し、関連する定義や実装に素早くアクセスできるツールが必要であると認識していました。特に、Goの標準ライブラリやプロジェクト内の他のGoファイルへのナビゲーションは、開発効率に直結する重要な要素でした。
このコミット以前は、GDSは単一のGoファイルをコンパイルして表示する機能が主であり、ディレクトリ構造を視覚的に探索する手段がありませんでした。また、ソースコード内に記述されたパッケージ名などへのリンクも、適切に解決されていませんでした。これらの課題を解決し、GDSをより「実用的な」ドキュメンテーションツールにするために、本コミットが導入されました。ロギングの導入は、サーバーの動作状況を把握し、デバッグを容易にするための一般的な改善策です。
前提知識の解説
このコミットを理解するためには、以下の概念とGo言語の初期の設計に関する知識が役立ちます。
-
Go言語の初期開発とツールチェイン:
- Go言語は2009年にGoogleで公開されました。このコミットはその直後の非常に初期の段階のものであり、言語仕様や標準ライブラリ、ツールチェインがまだ活発に開発・変更されていた時期です。
- 当時のGoの標準ライブラリやパッケージ構造は、現在のものとは異なる部分が多く存在します。例えば、
log
パッケージは現在とほぼ同じですが、os
パッケージのAPIは現在とは異なる可能性があります。 http
パッケージも同様に、基本的な機能は備えていたものの、現在のnet/http
パッケージのような洗練されたミドルウェアやルーティングの概念はまだ確立されていなかったかもしれません。
-
Go Doc Server (GDS):
- Go言語には、ソースコードからドキュメンテーションを生成し、Webブラウザで閲覧可能にする
go doc
コマンドと、それをWebサーバーとして提供するgodoc
ツールが存在します。このコミットで言及されているGDSは、godoc
ツールの前身、あるいはそのプロトタイプのようなものと考えられます。 - GDSの主な目的は、Goのソースコードを解析し、HTML形式で整形してブラウザに表示することです。これには、コードのシンタックスハイライト、定義へのリンク、パッケージ構造の表示などが含まれます。
- Go言語には、ソースコードからドキュメンテーションを生成し、Webブラウザで閲覧可能にする
-
os
パッケージとファイルシステム操作:- Goの
os
パッケージは、オペレーティングシステムとのインタラクション、特にファイルシステム操作(ファイルの読み書き、ディレクトリの作成・削除、ファイル情報の取得など)を提供します。 os.Open
はファイルやディレクトリを開くために使用され、os.Readdir
はディレクトリ内のエントリ(ファイルやサブディレクトリ)を読み取るために使用されます。os.Dir
構造体(現在のos.FileInfo
インターフェースに相当)は、ファイル名、サイズ、パーミッション、ディレクトリかどうかなどの情報を提供します。
- Goの
-
net/http
パッケージとWebサーバー:- Goの
net/http
パッケージは、HTTPクライアントとサーバーの実装を提供します。 http.Handle
は特定のURLパスに対するハンドラ関数を登録するために使用されます。http.ListenAndServe
は指定されたポートでHTTPサーバーを起動し、リクエストを処理します。http.Conn
はHTTP接続を表し、fmt.Fprintf
などを用いてクライアントに応答を書き込むために使用されます。http.Request
は受信したHTTPリクエストの詳細(URL、ヘッダーなど)を含みます。
- Goの
-
sort
パッケージ:- Goの
sort
パッケージは、スライスやカスタムコレクションをソートするための汎用的なインターフェースとアルゴリズムを提供します。 sort.Interface
インターフェース(Len()
,Less(i, j int)
,Swap(i, j int)
の3つのメソッドを持つ)を実装することで、任意の型をソート可能にします。
- Goの
-
log
パッケージ:- Goの
log
パッケージは、簡単なロギング機能を提供します。log.Stdoutf
は標準出力にフォーマットされた文字列を出力します。log.Exitf
はメッセージを出力した後にプログラムを終了します。
- Goの
-
パスの正規化とサニタイズ:
- Webアプリケーションにおいて、ユーザーからの入力パスを直接ファイルシステムパスとして使用することはセキュリティ上のリスク(ディレクトリトラバーサルなど)を伴います。そのため、パスを正規化(例:
//
を/
に、/../
を適切に解決)し、安全な形式にサニタイズする処理は非常に重要です。
- Webアプリケーションにおいて、ユーザーからの入力パスを直接ファイルシステムパスとして使用することはセキュリティ上のリスク(ディレクトリトラバーサルなど)を伴います。そのため、パスを正規化(例:
技術的詳細
このコミットは、GDSのWebサーバーとしての振る舞いを大きく変更し、ファイルシステム上のディレクトリ構造をWebブラウザで探索できるようにするものです。
-
ディレクトリリスティング機能の追加 (
serveDir
関数):serveDir
関数が新しく導入されました。この関数は、指定されたディレクトリの内容を読み込み、HTML形式でブラウザに表示します。os.Open
とos.Readdir
を使用してディレクトリ内のエントリ(ファイルやサブディレクトリ)を取得します。- 取得したエントリは、新しく定義された
DirArray
型(sort.Interface
を実装)を使用して名前順にソートされます。これにより、ディレクトリリスティングがアルファベット順に表示され、ユーザーにとって見やすくなります。 - 表示は3つのセクションに分けられます:
- ディレクトリ: サブディレクトリはリンクとして表示され、クリックするとそのサブディレクトリの内容に移動できます。
- .goファイル: Goソースファイルもリンクとして表示され、クリックするとそのGoファイルのコンパイル済み(整形済み)内容が表示されます。
- その他のファイル: Goファイルでもディレクトリでもないファイルは、リンクなしのグレーアウトされたテキストとして表示されます。これは、GDSが主にGoソースコードの閲覧に特化しているため、その他のファイルタイプは直接表示しないという設計思想を示しています。
printLink
ヘルパー関数が導入され、HTMLの<a>
タグを生成してリンクを簡単に作成できるようになりました。
-
リクエストハンドリングの改善 (
serve
関数):- 以前の
docServer
関数に代わり、serve
関数がHTTPリクエストのメインハンドラとして機能します。 serve
関数は、リクエストされたURLパスがディレクトリであるか、Goファイルであるかをos.Stat
とisGoFile
関数を使って判断します。- パスがディレクトリであれば
serveDir
を呼び出し、GoファイルであればserveFile
(旧docServer
)を呼び出します。 - それ以外の場合は、
http.StatusNotFound
を返します。これにより、サーバーはGoソースコードとディレクトリの閲覧に特化し、他のファイルタイプへの直接アクセスを制限します。
- 以前の
-
パスのサニタイズ (
utils.go
のcleanPath
とSanitizePath
):usr/gri/pretty/utils.go
にcleanPath
とSanitizePath
という新しい関数が追加されました。cleanPath
は、パス内の連続するスラッシュ(例://
,///
)を単一のスラッシュに正規化します。SanitizePath
はcleanPath
を呼び出した後、パスの末尾のスラッシュを削除します(ただし、ルートパス/
の場合は削除しない)。- これらの関数は、ユーザーからのURLパス入力を安全に処理し、ファイルシステムパスとして使用する前に正規化するために導入されました。これはセキュリティとパスの一貫性を保つ上で非常に重要です。
-
ロギングの導入:
log
パッケージがインポートされ、fmt.Printf
の代わりにlog.Stdoutf
が冗長モード(-v
フラグ)での出力に使用されるようになりました。- サーバー起動時のエラーハンドリングも
panic
からlog.Exitf
に変更され、より適切なエラー報告とプログラム終了が行われるようになりました。
-
リンク生成の修正 (
printer.go
):usr/gri/pretty/printer.go
内のHTMLリンク生成ロジックが修正され、ハードコードされていたhttp://localhost:6060/gds/src/lib/
のようなプレフィックスが/src/lib/
に短縮されました。これは、gds.go
でhttp.Handle("/", ...)
が設定されたことと整合性が取れており、サーバーがルートパスからコンテンツを提供するようになったことを反映しています。
-
urlPrefix
の削除:- 以前は
urlPrefix = "/gds"
という変数が存在し、URLパスの処理に使用されていましたが、新しいserve
関数がルートパスからリクエストを処理するようになったため、この変数は不要となり削除されました。
- 以前は
コアとなるコードの変更箇所
usr/gri/pretty/gds.go
import
に"sort"
と"log"
が追加。var urlPrefix
が削除。var root
の初期化がflag.String
に変更され、コマンドライン引数でルートディレクトリを指定可能に。getFilename
関数が削除され、URLパスの処理ロジックがserve
関数内に統合。DirArray
型(sort.Interface
実装)が追加され、ディレクトリ内のエントリをソート可能に。isGoFile
関数が追加され、ファイルがGoソースファイルであるかを判定。printLink
関数が追加され、HTMLリンクの生成を簡素化。serveDir
関数が新しく追加され、ディレクトリの内容をHTMLで表示するロジックを実装。docServer
関数がserveFile
関数に実質的に置き換えられ、ファイルコンパイルと表示ロジックを担当。serve
関数が新しく追加され、HTTPリクエストのルーティング(ディレクトリかGoファイルか)を担当。main
関数内で、ロギングにlog.Stdoutf
を使用するよう変更。main
関数内で、http.Handle(urlPrefix + "/", ...)
がhttp.Handle("/", ...)
に変更され、サーバーがルートパスからリクエストを処理するよう設定。main
関数内で、ルートディレクトリの存在チェックとサニタイズ処理が追加。
usr/gri/pretty/printer.go
HtmlPackageName
関数内のリンク生成部分が変更され、http://localhost:6060/gds/src/lib/
のような絶対パス指定が/src/lib/
という相対パス指定に修正。
usr/gri/pretty/utils.go
cleanPath
関数が追加され、パス内の連続するスラッシュを単一に正規化。SanitizePath
関数が追加され、cleanPath
を呼び出した後、末尾のスラッシュを削除してパスをサニタイズ。
コアとなるコードの解説
usr/gri/pretty/gds.go
type DirArray []os.Dir
と func (p DirArray) Len() int
, func (p DirArray) Less(i, j int) bool
, func (p DirArray) Swap(i, j int)
これはGoのsort
パッケージが提供するsort.Interface
インターフェースの実装です。os.Dir
(現在のos.FileInfo
に相当)のスライスであるDirArray
を定義し、そのLen
、Less
、Swap
メソッドを実装することで、sort.Sort(DirArray(list))
のようにしてディレクトリ内のエントリを名前順にソートできるようになります。Less
メソッドはp[i].Name < p[j].Name
と定義されており、アルファベット順にソートされます。
func serveDir(c *http.Conn, dirname string)
この関数は、指定されたdirname
(ディレクトリのパス)の内容を読み込み、HTTP接続c
にHTML形式で出力します。
os.Open(*root + dirname, os.O_RDONLY, 0)
:root
(サーバーのルートディレクトリ)とdirname
を結合した絶対パスでディレクトリを開きます。- エラーが発生した場合(ディレクトリが見つからないなど)は、
http.StatusNotFound
を返し、エラーメッセージを出力します。 os.Readdir(fd, -1)
: 開いたディレクトリfd
から全てのエントリを読み込みます。sort.Sort(DirArray(list))
: 読み込んだエントリのリストを名前順にソートします。c.SetHeader("content-type", "text/html; charset=utf-8")
: レスポンスのContent-TypeをHTMLに設定します。fmt.Fprintf(c, "<b>%s</b>\n", path)
: 現在のディレクトリパスを太字で表示します。- 3つのセクションでの表示:
- ディレクトリ:
entry.IsDirectory()
でディレクトリを判別し、printLink
を使ってリンクとして表示します。 - .goファイル:
isGoFile(&entry)
でGoファイルを判別し、printLink
を使ってリンクとして表示します。 - その他のファイル: ディレクトリでもGoファイルでもないエントリは、
fmt.Fprintf(c, "<font color=grey>%s</font><br>\n", entry.Name)
でグレーアウトされたテキストとして表示されます。
- ディレクトリ:
func serve(c *http.Conn, req *http.Request)
この関数は、すべてのHTTPリクエストを処理するメインハンドラです。
log.Stdoutf("URL = %s\n", req.RawUrl)
: 冗長モードの場合、リクエストされたURLをログに出力します。path := Utils.SanitizePath(req.Url.Path)
: リクエストURLのパス部分をUtils.SanitizePath
でサニタイズします。これにより、パスの正規化とセキュリティが確保されます。dir, err := os.Stat(*root + path)
: サニタイズされたパスとルートディレクトリを結合した絶対パスに対してos.Stat
を実行し、ファイルまたはディレクトリの情報を取得します。- エラーが発生した場合(ファイルやディレクトリが見つからないなど)は、
http.StatusNotFound
を返し、エラーメッセージを出力します。 switch
文でdir
のタイプをチェックし、適切なハンドラを呼び出します。case dir.IsDirectory()
: パスがディレクトリであればserveDir(c, path)
を呼び出します。case isGoFile(dir)
: パスがGoファイルであればserveFile(c, path)
を呼び出します。default
: それ以外の場合は、http.StatusNotFound
を返します。
usr/gri/pretty/utils.go
func cleanPath(s string) string
この関数は、文字列s
内の連続するスラッシュ(例: a//b
, a///b
)を単一のスラッシュに変換します。再帰的に実装されており、パスを走査して連続するスラッシュを見つけると、その部分を単一のスラッシュに置き換え、残りの部分に対して再帰的に処理を続けます。
func SanitizePath(s string) string
この関数は、パス文字列s
を安全な形式にサニタイズします。
s = cleanPath(s)
: まずcleanPath
を呼び出して、連続するスラッシュを正規化します。if s[len(s)-1] == '/'
: パスの末尾がスラッシュであるかをチェックします。s = s[0 : len(s)-1]
: 末尾にスラッシュがあれば削除します。ただし、これはルートパス/
の場合には適用されません(len(s)-1
が0になるため)。
これらの関数は、Webリクエストから受け取ったパスをファイルシステム操作に安全に利用するための重要な前処理です。
関連リンク
- Go言語の公式ドキュメンテーション: https://go.dev/doc/
net/http
パッケージのドキュメンテーション: https://pkg.go.dev/net/httpos
パッケージのドキュメンテーション: https://pkg.go.dev/ossort
パッケージのドキュメンテーション: https://pkg.go.dev/sortlog
パッケージのドキュメンテーション: https://pkg.go.dev/log
参考にした情報源リンク
- Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語の
godoc
ツールの歴史と機能に関する情報 (Goブログなど): https://go.dev/blog/godoc - Go言語の
net/http
パッケージの進化に関する情報 (Goブログ、GoのIssueトラッカーなど) - Go言語の
os
パッケージの進化に関する情報 (Goブログ、GoのIssueトラッカーなど) - 一般的なWebセキュリティにおけるパスのサニタイズに関する情報