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

[インデックス 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/htmlimage/png)。

このコミットが作成される以前は、mime パッケージの TypeByExtension 関数は、主にUnix系システムで一般的な /etc/mime.types や Apache の設定ファイルなど、特定のMIMEタイプ定義ファイルから情報を読み込むことに依存していました。しかし、Windows環境ではこれらのファイルが存在しないか、MIMEタイプの管理方法が異なります。Windowsでは、MIMEタイプ情報は通常、システムレジストリに格納されています。

そのため、Windows上でGoアプリケーションがファイル拡張子から正確なMIMEタイプを取得するためには、Windowsレジストリを照会するメカニズムが必要でした。このコミットは、このプラットフォーム間の差異を吸収し、mime パッケージがWindows環境でも期待通りに機能するようにするために導入されました。これにより、Goアプリケーションのクロスプラットフォーム互換性が向上します。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

  1. MIMEタイプ:

    • インターネットメディアタイプとも呼ばれ、ファイルやデータの種類を識別するための標準です。例えば、.html ファイルは text/html.png 画像は image/png といったMIMEタイプを持ちます。
    • Webサーバーがクライアントにファイルを送信する際、MIMEタイプをHTTPヘッダーに含めることで、ブラウザがそのファイルをどのように扱うべきかを判断できます。
  2. Go言語の syscall パッケージ:

    • Go言語の syscall パッケージは、オペレーティングシステム(OS)の低レベルなシステムコールにアクセスするための機能を提供します。これにより、GoプログラムからOS固有のAPI(例えば、ファイルシステム操作、ネットワーク通信、プロセス管理など)を直接呼び出すことが可能になります。
    • Windowsの場合、syscall パッケージはWin32 APIへのバインディングを提供します。
  3. Windowsレジストリ:

    • Windowsオペレーティングシステムが設定情報やオプションを格納するために使用する階層型データベースです。
    • アプリケーションのMIMEタイプ関連情報(例: ファイル拡張子とそれに対応するMIMEタイプ)は、通常 HKEY_CLASSES_ROOT というルートキーの下に格納されています。
    • レジストリは、キー(フォルダのようなもの)と値(データ)で構成されます。各キーはサブキーを持つことができ、値は文字列、バイナリデータ、DWORD(32ビット整数)などの形式で格納されます。
  4. Go言語のビルドタグ (Build Tags):

    • Go言語では、ファイル名に _GOOS_GOARCH のようなサフィックスを付けることで、特定のOSやアーキテクチャでのみコンパイルされるファイルを指定できます。例えば、type_unix.go はUnix系システムでのみ、type_windows.go はWindowsでのみコンパイルされます。
    • これにより、プラットフォーム固有のコードを分離し、クロスプラットフォーム対応のアプリケーションを容易に開発できます。

技術的詳細

このコミットの技術的な核心は、mime パッケージのMIMEタイプ解決ロジックをプラットフォーム固有のファイルに分離し、Windows向けにレジストリベースの実装を導入した点にあります。

  1. プラットフォーム固有のMIMEタイプ解決:

    • 従来の src/pkg/mime/type.go にあったMIMEタイプ定義ファイルの読み込みロジック(typeFiles 変数と loadMimeFile 関数)は、src/pkg/mime/type_unix.go という新しいファイルに移動されました。このファイルは、GOOSfreebsd, darwin, linux, openbsd, plan9 の場合にのみコンパイルされます。
    • Windows向けには、src/pkg/mime/type_windows.go という新しいファイルが作成されました。このファイルは、GOOSwindows の場合にのみコンパイルされます。
  2. WindowsレジストリからのMIMEタイプ取得:

    • src/pkg/mime/type_windows.go 内の initMime 関数は、Windowsレジストリを操作してMIMEタイプ情報を取得します。
    • 具体的には、HKEY_CLASSES_ROOT レジストリキーを開き、その下のサブキー(ファイル拡張子に対応するキー、例: .html)を列挙します。
    • 各拡張子キーに対して、Content Type という名前の値が存在するかどうかを確認します。この値がMIMEタイプ文字列(例: text/html)を格納しています。
    • レジストリ操作には、syscall パッケージを通じてWin32 API関数が使用されます。
  3. 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関数(SyscallSyscall6 などを用いた低レベルなシステムコール呼び出し)として自動生成されます。
    • また、レジストリキーや値のタイプを示す定数(HKEY_CLASSES_ROOT, KEY_READ, REG_SZ など)が src/pkg/syscall/ztypes_windows.go に追加されました。
  4. ビルドシステムの変更:

    • src/pkg/mime/Makefile が更新され、GOFILES_$(GOOS) という変数を使って、現在のOS (GOOS) に応じて type_unix.go または type_windows.go がビルドに含まれるように変更されました。

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

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

  1. src/pkg/mime/Makefile:

    • GOFILES_freebsd, GOFILES_darwin, GOFILES_linux, GOFILES_openbsd, GOFILES_plan9type_unix.go を追加。
    • GOFILES_windowstype_windows.go を追加。
    • GOFILES+=$(GOFILES_$(GOOS)) を追加し、OSに応じたファイルをビルドに含めるように変更。
  2. src/pkg/mime/type.go:

    • typeFiles 変数、loadMimeFile 関数、および initMime 関数の本体が削除され、プラットフォーム固有のファイルに移動。
    • TypeByExtension 関数のコメントが更新され、WindowsシステムではレジストリからMIMEタイプが抽出されることが明記された。
    • initMime の呼び出しは once.Do(initMime) のままだが、実際の initMime の実装はOSによって異なるファイルに存在する。
  3. src/pkg/mime/type_unix.go (新規ファイル):

    • type.go から移動された typeFiles 変数、loadMimeFile 関数、およびUnix系OS向けの initMime 関数が含まれる。
    • テスト用の initMimeForTests 関数も含まれる。
  4. src/pkg/mime/type_windows.go (新規ファイル):

    • WindowsレジストリからMIMEタイプを読み込むための initMime 関数が実装されている。
    • syscall パッケージの関数(RegOpenKeyEx, RegEnumKeyEx, RegQueryValueEx など)を使用してレジストリを操作する。
    • Windows向けのテスト用 initMimeForTests 関数も含まれる。
  5. src/pkg/syscall/syscall_windows.go:

    • Windowsレジストリ操作に必要なWin32 API関数へのGoバインディングが //sys ディレクティブとして追加された。
      • RegOpenKeyEx
      • RegCloseKey
      • RegQueryInfoKey
      • RegEnumKeyEx
      • RegQueryValueEx
  6. src/pkg/syscall/zsyscall_windows_386.go および src/pkg/syscall/zsyscall_windows_amd64.go:

    • 上記 syscall_windows.go で宣言されたWin32 API関数の実際の呼び出しロジック(NewProcSyscall を使用)が、それぞれのアーキテクチャ向けに自動生成されたファイルに追加された。
  7. src/pkg/syscall/ztypes_windows.go:

    • Windowsレジストリ操作に関連する定数(HKEY_CLASSES_ROOT, KEY_READ, REG_SZ など)が追加された。

コアとなるコードの解説

src/pkg/mime/type_windows.goinitMime 関数

この関数は、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を安全かつ効率的に呼び出すことが可能になります。

関連リンク

参考にした情報源リンク