[インデックス 10437] ファイルの概要
このコミットは、Go言語の標準ライブラリである mime
パッケージにおいて、ファイル拡張子からMIMEタイプを判別する TypeByExtension
関数にWindows固有の実装を追加するものです。これにより、Windows環境でもMIMEタイプが適切に解決されるようになります。具体的には、Unix系システムで利用される mime.types
ファイルからの読み込みに加えて、WindowsレジストリからMIMEタイプ情報を取得する機能が導入されました。
コミット
commit ac17fd4cd2daba25471c07d25d618171e905fd2d
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Fri Nov 18 10:07:36 2011 +1100
mime: implement TypeByExtension for windows
Fixes #2071.
R=golang-dev, hcwfrichter, pascal, rsc
CC=golang-dev
https://golang.org/cl/5369056
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ac17fd4cd2daba25471c07d25d618171e905fd2d
元コミット内容
このコミットの元の内容は、Go言語の mime
パッケージにWindows向けの TypeByExtension
の実装を追加することです。これは、Issue #2071 を解決することを目的としています。
変更の背景
Go言語の mime
パッケージは、ファイル拡張子に基づいてMIMEタイプ(Multipurpose Internet Mail Extensions)を決定する機能を提供します。MIMEタイプは、インターネット上でファイルの種類を識別するために使用される標準的な方法です(例: text/html
、image/png
)。
このコミットが作成される以前は、mime
パッケージの TypeByExtension
関数は、主にUnix系システムで一般的な /etc/mime.types
や Apache の設定ファイルなど、特定のMIMEタイプ定義ファイルから情報を読み込むことに依存していました。しかし、Windows環境ではこれらのファイルが存在しないか、MIMEタイプの管理方法が異なります。Windowsでは、MIMEタイプ情報は通常、システムレジストリに格納されています。
そのため、Windows上でGoアプリケーションがファイル拡張子から正確なMIMEタイプを取得するためには、Windowsレジストリを照会するメカニズムが必要でした。このコミットは、このプラットフォーム間の差異を吸収し、mime
パッケージがWindows環境でも期待通りに機能するようにするために導入されました。これにより、Goアプリケーションのクロスプラットフォーム互換性が向上します。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
-
MIMEタイプ:
- インターネットメディアタイプとも呼ばれ、ファイルやデータの種類を識別するための標準です。例えば、
.html
ファイルはtext/html
、.png
画像はimage/png
といったMIMEタイプを持ちます。 - Webサーバーがクライアントにファイルを送信する際、MIMEタイプをHTTPヘッダーに含めることで、ブラウザがそのファイルをどのように扱うべきかを判断できます。
- インターネットメディアタイプとも呼ばれ、ファイルやデータの種類を識別するための標準です。例えば、
-
Go言語の
syscall
パッケージ:- Go言語の
syscall
パッケージは、オペレーティングシステム(OS)の低レベルなシステムコールにアクセスするための機能を提供します。これにより、GoプログラムからOS固有のAPI(例えば、ファイルシステム操作、ネットワーク通信、プロセス管理など)を直接呼び出すことが可能になります。 - Windowsの場合、
syscall
パッケージはWin32 APIへのバインディングを提供します。
- Go言語の
-
Windowsレジストリ:
- Windowsオペレーティングシステムが設定情報やオプションを格納するために使用する階層型データベースです。
- アプリケーションのMIMEタイプ関連情報(例: ファイル拡張子とそれに対応するMIMEタイプ)は、通常
HKEY_CLASSES_ROOT
というルートキーの下に格納されています。 - レジストリは、キー(フォルダのようなもの)と値(データ)で構成されます。各キーはサブキーを持つことができ、値は文字列、バイナリデータ、DWORD(32ビット整数)などの形式で格納されます。
-
Go言語のビルドタグ (Build Tags):
- Go言語では、ファイル名に
_GOOS
や_GOARCH
のようなサフィックスを付けることで、特定のOSやアーキテクチャでのみコンパイルされるファイルを指定できます。例えば、type_unix.go
はUnix系システムでのみ、type_windows.go
はWindowsでのみコンパイルされます。 - これにより、プラットフォーム固有のコードを分離し、クロスプラットフォーム対応のアプリケーションを容易に開発できます。
- Go言語では、ファイル名に
技術的詳細
このコミットの技術的な核心は、mime
パッケージのMIMEタイプ解決ロジックをプラットフォーム固有のファイルに分離し、Windows向けにレジストリベースの実装を導入した点にあります。
-
プラットフォーム固有のMIMEタイプ解決:
- 従来の
src/pkg/mime/type.go
にあったMIMEタイプ定義ファイルの読み込みロジック(typeFiles
変数とloadMimeFile
関数)は、src/pkg/mime/type_unix.go
という新しいファイルに移動されました。このファイルは、GOOS
がfreebsd
,darwin
,linux
,openbsd
,plan9
の場合にのみコンパイルされます。 - Windows向けには、
src/pkg/mime/type_windows.go
という新しいファイルが作成されました。このファイルは、GOOS
がwindows
の場合にのみコンパイルされます。
- 従来の
-
WindowsレジストリからのMIMEタイプ取得:
src/pkg/mime/type_windows.go
内のinitMime
関数は、Windowsレジストリを操作してMIMEタイプ情報を取得します。- 具体的には、
HKEY_CLASSES_ROOT
レジストリキーを開き、その下のサブキー(ファイル拡張子に対応するキー、例:.html
)を列挙します。 - 各拡張子キーに対して、
Content Type
という名前の値が存在するかどうかを確認します。この値がMIMEタイプ文字列(例:text/html
)を格納しています。 - レジストリ操作には、
syscall
パッケージを通じてWin32 API関数が使用されます。
-
syscall
パッケージの拡張:- Windowsレジストリを操作するために必要なWin32 API関数(
RegOpenKeyExW
,RegCloseKey
,RegQueryInfoKeyW
,RegEnumKeyExW
,RegQueryValueExW
)が、src/pkg/syscall/syscall_windows.go
に//sys
ディレクティブとして宣言されました。 - これらの宣言は、Goのツールチェーンによって
src/pkg/syscall/zsyscall_windows_386.go
およびsrc/pkg/syscall/zsyscall_windows_amd64.go
に対応するGo関数(Syscall
やSyscall6
などを用いた低レベルなシステムコール呼び出し)として自動生成されます。 - また、レジストリキーや値のタイプを示す定数(
HKEY_CLASSES_ROOT
,KEY_READ
,REG_SZ
など)がsrc/pkg/syscall/ztypes_windows.go
に追加されました。
- Windowsレジストリを操作するために必要なWin32 API関数(
-
ビルドシステムの変更:
src/pkg/mime/Makefile
が更新され、GOFILES_$(GOOS)
という変数を使って、現在のOS (GOOS
) に応じてtype_unix.go
またはtype_windows.go
がビルドに含まれるように変更されました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
-
src/pkg/mime/Makefile
:GOFILES_freebsd
,GOFILES_darwin
,GOFILES_linux
,GOFILES_openbsd
,GOFILES_plan9
にtype_unix.go
を追加。GOFILES_windows
にtype_windows.go
を追加。GOFILES+=$(GOFILES_$(GOOS))
を追加し、OSに応じたファイルをビルドに含めるように変更。
-
src/pkg/mime/type.go
:typeFiles
変数、loadMimeFile
関数、およびinitMime
関数の本体が削除され、プラットフォーム固有のファイルに移動。TypeByExtension
関数のコメントが更新され、WindowsシステムではレジストリからMIMEタイプが抽出されることが明記された。initMime
の呼び出しはonce.Do(initMime)
のままだが、実際のinitMime
の実装はOSによって異なるファイルに存在する。
-
src/pkg/mime/type_unix.go
(新規ファイル):type.go
から移動されたtypeFiles
変数、loadMimeFile
関数、およびUnix系OS向けのinitMime
関数が含まれる。- テスト用の
initMimeForTests
関数も含まれる。
-
src/pkg/mime/type_windows.go
(新規ファイル):- WindowsレジストリからMIMEタイプを読み込むための
initMime
関数が実装されている。 syscall
パッケージの関数(RegOpenKeyEx
,RegEnumKeyEx
,RegQueryValueEx
など)を使用してレジストリを操作する。- Windows向けのテスト用
initMimeForTests
関数も含まれる。
- WindowsレジストリからMIMEタイプを読み込むための
-
src/pkg/syscall/syscall_windows.go
:- Windowsレジストリ操作に必要なWin32 API関数へのGoバインディングが
//sys
ディレクティブとして追加された。RegOpenKeyEx
RegCloseKey
RegQueryInfoKey
RegEnumKeyEx
RegQueryValueEx
- Windowsレジストリ操作に必要なWin32 API関数へのGoバインディングが
-
src/pkg/syscall/zsyscall_windows_386.go
およびsrc/pkg/syscall/zsyscall_windows_amd64.go
:- 上記
syscall_windows.go
で宣言されたWin32 API関数の実際の呼び出しロジック(NewProc
とSyscall
を使用)が、それぞれのアーキテクチャ向けに自動生成されたファイルに追加された。
- 上記
-
src/pkg/syscall/ztypes_windows.go
:- Windowsレジストリ操作に関連する定数(
HKEY_CLASSES_ROOT
,KEY_READ
,REG_SZ
など)が追加された。
- Windowsレジストリ操作に関連する定数(
コアとなるコードの解説
src/pkg/mime/type_windows.go
の initMime
関数
この関数は、Windows環境でMIMEタイプ情報を初期化する際の中心的なロジックを含んでいます。
func initMime() {
var root syscall.Handle
// HKEY_CLASSES_ROOT レジストリキーを開く
if syscall.RegOpenKeyEx(syscall.HKEY_CLASSES_ROOT, syscall.StringToUTF16Ptr(`\`),
0, syscall.KEY_READ, &root) != 0 {
return // 開けなければ終了
}
defer syscall.RegCloseKey(root) // 関数終了時にキーを閉じる
var count uint32
// HKEY_CLASSES_ROOT のサブキーの数を取得
if syscall.RegQueryInfoKey(root, nil, nil, nil, &count, nil, nil, nil, nil, nil, nil, nil) != 0 {
return // 取得できなければ終了
}
var buf [1 << 10]uint16 // バッファ (1KB)
for i := uint32(0); i < count; i++ {
n := uint32(len(buf))
// サブキーを列挙 (ファイル拡張子キーを探す)
if syscall.RegEnumKeyEx(root, i, &buf[0], &n, nil, nil, nil, nil) != 0 {
continue // 列挙できなければ次へ
}
ext := syscall.UTF16ToString(buf[:]) // UTF-16からGo文字列に変換
if len(ext) < 2 || ext[0] != '.' { // 拡張子(例: ".html")のみを対象とする
continue
}
var h syscall.Handle
// 拡張子キー(例: ".html")を開く
if syscall.RegOpenKeyEx(
syscall.HKEY_CLASSES_ROOT, syscall.StringToUTF16Ptr(`\`+ext),
0, syscall.KEY_READ, &h) != 0 {
continue // 開けなければ次へ
}
var typ uint32
n = uint32(len(buf) * 2) // APIはバイト配列を期待
// "Content Type" の値を取得
if syscall.RegQueryValueEx(
h, syscall.StringToUTF16Ptr("Content Type"),
nil, &typ, (*byte)(unsafe.Pointer(&buf[0])), &n) != 0 {
syscall.RegCloseKey(h)
continue // 取得できなければ次へ
}
syscall.RegCloseKey(h) // キーを閉じる
if typ != syscall.REG_SZ { // 文字列タイプ(REG_SZ)のみを処理
continue
}
mimeType := syscall.UTF16ToString(buf[:]) // UTF-16からGo文字列に変換
setExtensionType(ext, mimeType) // 取得したMIMEタイプを内部マップに設定
}
}
このコードは、Windowsレジストリの HKEY_CLASSES_ROOT
を走査し、ファイル拡張子(例: .txt
, .html
)に対応するキーを見つけます。そして、そのキーに格納されている Content Type
という名前の値を読み取り、それをMIMEタイプとしてGoの mime
パッケージの内部マップに登録します。これにより、TypeByExtension
関数がWindows環境で正しく機能するようになります。
src/pkg/syscall/syscall_windows.go
の //sys
ディレクティブ
//sys RegOpenKeyEx(key Handle, subkey *uint16, options uint32, desiredAccess uint32, result *Handle) (regerrno uintptr) = advapi32.RegOpenKeyExW
//sys RegCloseKey(key Handle) (regerrno uintptr) = advapi32.RegCloseKey
//sys RegQueryInfoKey(key Handle, class *uint16, classLen *uint32, reserved *uint32, subkeysLen *uint32, maxSubkeyLen *uint32, maxClassLen *uint32, valuesLen *uint32, maxValueNameLen *uint32, maxValueLen *uint32, saLen *uint32, lastWriteTime *Filetime) (regerrno uintptr) = advapi32.RegQueryInfoKeyW
//sys RegEnumKeyEx(key Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, class *uint16, classLen *uint32, lastWriteTime *Filetime) (regerrno uintptr) = advapi32.RegEnumKeyExW
//sys RegQueryValueEx(key Handle, name *uint16, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno uintptr) = advapi32.RegQueryValueExW
これらの行は、Goの syscall
パッケージがWindowsの advapi32.dll
に含まれるレジストリ関連のWin32 API関数を呼び出すための宣言です。Goのビルドシステムはこれらの宣言を解析し、対応するGo関数を自動生成します。これにより、Goプログラムから直接これらの低レベルなWindows APIを安全かつ効率的に呼び出すことが可能になります。
関連リンク
- Go言語の
mime
パッケージのドキュメント: https://pkg.go.dev/mime - Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Go言語のビルドタグに関するドキュメント: https://pkg.go.dev/cmd/go#hdr-Build_constraints
- Windows レジストリ (Microsoft Learn): https://learn.microsoft.com/ja-jp/windows/win32/sysinfo/registry
- MIMEタイプ (MDN Web Docs): https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types
参考にした情報源リンク
- Go言語の公式リポジトリ (GitHub): https://github.com/golang/go
- Go言語のIssueトラッカー (Issue #2071): https://github.com/golang/go/issues/2071
- Gerrit Change-Id (Goのコードレビューシステム): https://golang.org/cl/5369056
- Win32 API ドキュメント (Microsoft Learn):
RegOpenKeyEx
: https://learn.microsoft.com/ja-jp/windows/win32/api/winreg/nf-winreg-regopenkeyexwRegEnumKeyEx
: https://learn.microsoft.com/ja-jp/windows/win32/api/winreg/nf-winreg-regenumkeyexwRegQueryValueEx
: https://learn.microsoft.com/ja-jp/windows/win32/api/winreg/nf-winreg-regqueryvalueexwRegCloseKey
: https://learn.microsoft.com/ja-jp/windows/win32/api/winreg/nf-winreg-regclosekeyRegQueryInfoKey
: https://learn.microsoft.com/ja-jp/windows/win32/api/winreg/nf-winreg-regqueryinfokeyw